('smd_gallery',1,'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),('smd_lib',1,'Stef Dawson','http://stefdawson.com/','0.36','Shared function library used by smd_ plugins and others.','<div id=\"smd_help\">\r\n\r\n	<h1>smd_ plugin library</h1>\r\n\r\n	<p>Offers no public textpattern tags. It is simply a shared library of common functions used by smd_ plugins.</p>\r\n\r\n	<h2>Changelog</h2>\r\n\r\n	<ul>\r\n		<li>v0.1   07-02-25 Initial public release</li>\r\n		<li>v0.2   07-03-03 Added: <span class=\"caps\"><span class=\"caps\">MLP</span></span> (Multi-Lingual Pack) library support</li>\r\n		<li>v0.21  07-03-06 Added: integer range functionality. <code>smd_getAtts</code> now takes a RegEx arg</li>\r\n		<li>v0.21a 07-03-21 Fixed: Character ranges ignored (thanks mrdale)</li>\r\n		<li>v0.21b 07-04-02 Fixed: Sticky article support in <code>smd_getAtts</code> (thanks pieman)</li>\r\n		<li>v0.21c 07-07-29 Fixed: Numeric ranges in categories (thanks wolle)</li>\r\n		<li>v0.21d 07-08-03 Fixed: Negation with multiple elements</li>\r\n		<li>v0.21e 07-09-14 Fixed: Ability to leave empty splitRange parameters intact</li>\r\n		<li>v0.22  07-09-20 Fixed: Undefined index warnings (thanks Ambitiouslemon). Enhanced matches so spaces are allowed in strings (thanks DrRogg)</li>\r\n		<li>v0.23  07-04-09 <span class=\"caps\">BETA</span> : Added the FuzzyFind class and getWord function. getAtts() now allows <code>?q</code></li>\r\n		<li>v0.3   07-10-29 Rewrote <code>smd_getAtts</code> as <code>smd_getOpts</code> to allow replaced vars within text. Deprecated <code>smd_getAtts</code>, added <code>smd_pregPos</code>. Changed <code>smd_splitRange</code> as <code>smd_split</code> to allow ranges to be switched on or off. Deprecated <code>smd_splitRange</code>. Added generic <span class=\"caps\">MLP</span> class support (<code>smd_MLP</code>). Deprecated <code>smdMLP</code> array, <code>smd_gTxt</code> and <code>smd_getCaller</code>. Made <code>smd_FuzzyFind</code> and <code>smd_getWord</code> official</li>\r\n		<li>v0.31  07-11-27 Removed <code>smdMLP</code> array, <code>smd_gTxt</code> and <code>smd_getCaller</code>. Deprecated <code>smd_getSubCats</code>. Added a few PHP4 helper functions</li>\r\n		<li>v0.32  08-03-29 Removed <code>smd_getAtts</code> and <code>smd_getSubCats</code>. Deprecated <code>smd_getOpts</code> and <code>smd_split</code>. Added <code>smd_doList</code>. Moved the <code>smd_FuzzyFind</code> class and <code>smd_getWord</code> into the smd_fuzzy_find plugin where they should have been all along</li>\r\n		<li>v0.33  08-12-02 Undeprecated(!) <code>smd_split</code> since it&#8217;s actually quite useful ; extended <code>smd_doList</code> to encompass <code>$thisimage</code> (for future) and <code>$variable</code> ; fixed bug in <code>smd_doList</code> when using subcats</li>\r\n		<li>v0.34  08-12-13 <code>smd_doList</code> uses a unicode regex</li>\r\n		<li>v0.35  09-02-24 <code>smd_doList</code> fixed ranges in &#8216;?&#8217; variables (thanks koobs)</li>\r\n		<li>v0.36  09-04-02 <code>smd_doList</code> falls back to <span class=\"caps\">ASCII</span> if Unicode not available (thanks RedFox / mlarino / decoderltd)</li>\r\n	</ul>\r\n\r\n	<h2>Function Reference</h2>\r\n\r\n	<p><strong>smd_addQSVar</strong><br />\r\n<strong>smd_removeQSVar</strong></p>\r\n\r\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. &#8216;Add&#8217; takes three arguments, &#8216;Remove&#8217; just takes the first two:</p>\r\n\r\n	<ol>\r\n		<li>The <span class=\"caps\">URL</span> string to add to/remove from</li>\r\n		<li>The id of the querystring (the bit before the = sign)</li>\r\n		<li>The value of the new querystring (the bit after the = sign)</li>\r\n	</ol>\r\n\r\n	<p>e.g. <code>smd_addQSVar($thisarticle[&#39;url_title&#39;], &#39;tpg&#39;, 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>\r\n\r\n	<p><strong>smd_doList</strong></p>\r\n\r\n	<p>Return an expanded list of items with the following properties:</p>\r\n\r\n	<ol>\r\n		<li>Anything containing &#8216;?&#8217; or &#8216;!&#8217; 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>\r\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>\r\n		<li><span class=\"caps\">TXP</span> fields may themselves be lists or ranges</li>\r\n		<li>Anything that is not a <span class=\"caps\">TXP</span> field is used verbatim</li>\r\n		<li>The items are returned as 2 lists; inclusion and exclusion</li>\r\n	</ol>\r\n\r\n	<p>Args ( [*] = mandatory ) :\r\n	<ol>\r\n		<li>[*] lst = the list as a delimited string</li>\r\n		<li>rng = whether to allow ranges or not (bool). Default = true</li>\r\n		<li>sub = the type of subcategory to traverse (image, file, link, article, none=&#8221;&#8220;) and how many levels to go down (e.g. image:2). Default = &#8216;&#8217;</li>\r\n		<li>qte = whether to quote each item in the array or not (bool). Default = true</li>\r\n		<li>dlm = the delimiter (string). Default = &#8220;,&#8221;</li>\r\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 = &#8220;1&#8221;</li>\r\n	</ol></p>\r\n\r\n	<p><strong>smd_getOpts</strong></p>\r\n\r\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>\r\n\r\n	<ul>\r\n		<li>?c = current global category (!c = not current category)</li>\r\n		<li>?s = current section (!s = not current section)</li>\r\n		<li>?t = current article title (!t = not current title)</li>\r\n		<li>?id = current article ID, prepended with $idprefix (!id = not current ID)</li>\r\n		<li>?q = current query term (!q = not current query term)</li>\r\n		<li>?field = contents of the current article&#8217;s field (could be a comma-separated list)</li>\r\n		<li>!field = not the contents of the current article&#8217;s field (could be a comma-separated list)</li>\r\n	</ul>\r\n\r\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>\r\n\r\n	<p>Args ( [*] = mandatory ) :\r\n	<ol>\r\n		<li>[*] The string to search for matches</li>\r\n		<li>[*] An array containing shortcuts that are &#8220;allowed&#8221; to be found in the string (?c, ?s, ?t, ?field etc)</li>\r\n		<li>The prefix for ?id strings</li>\r\n		<li>Boolean indicating whether to allow range expansion or not</li>\r\n		<li>RegEx string to split options at (see smd_split)</li>\r\n		<li>preg_split option (see smd_split)</li>\r\n	</ol></p>\r\n\r\n	<p><strong>smd_split</strong></p>\r\n\r\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 &#8216;expanding&#8217; first (and $allowRange is true), they are dealt with. Takes the following arguments ( [*] = mandatory args) :</p>\r\n\r\n	<ol>\r\n		<li>[*] The string to split</li>\r\n		<li>Boolean indicating whether to allow range expansion or not (i.e. 1-5 becomes 1,2,3,4,5)</li>\r\n		<li>The regular expression character classes to match. If a full RegEx starting and ending with &#8216;/&#8217; characters is supplied, the expression is used verbatim. Without the &#8216;/&#8217; characters, the expression is treated as a list of character classes to find. Defaults to &#8220;/(,|,\\s)+/&#8221; which is a comma, or comma and a whitespace character.</li>\r\n		<li>preg_split option constant as defined in the php manual</li>\r\n	</ol>\r\n\r\n	<p><strong>smd_MLP</strong><br />\r\nInstantiate one of these to handle <acronym title=\"Multi-Lingual Pack\"><span class=\"caps\">MLP</span></acronym> in your plugin like this:</p>\r\n\r\n	<p>1) Declare a unique global variable, e.g. global $myPlug<br />\r\n2) Define your default string replacement array (doesn&#8217;t need to be global), e.g:</p>\r\n\r\n	<p>	$myStrings = array (&#8220;msg1&#8221; =&gt; &#8220;This is message 1&#8221;, &#8220;msg2&#8221; =&gt; &#8220;This is message 2&#8221;);</p>\r\n\r\n	<p>3) Create an <span class=\"caps\">MLP</span> handler:</p>\r\n\r\n	<p>	$myPlug = new smd_MLP(&#8220;plugin_name&#8221;, &#8220;plugin_prefix&#8221;, $myStrings);</p>\r\n\r\n	<p>4) That&#8217;s it! There are two optional args to smd_MLP:\r\n	a) the default (full) language to use, e.g &#8220;da-dk&#8221;. Defaults to &#8220;en-gb&#8221;.\r\n	b) the interface the strings are for. Choose from &#8220;public&#8221; (the default), &#8220;admin&#8221; or &#8220;common&#8221;</p>\r\n\r\n	<p>5) To use a replacement string in your code:\r\n	a) Make sure to import the unique global variable: e.g. global $myPlug;\r\n	b) Call $myPlug-&gt;gTxt(&#8220;messageID&#8221;); [ e.g. $myPlug-&gt;gTxt(&#8220;msg1&#8221;) ]\r\n	c) If you want to replace any args in your message string, pass an associative array as the 2nd arg to gTxt()</p>\r\n\r\n	<p><strong>smd_doDblQuote</strong></p>\r\n\r\n	<p>Alternative to the core&#8217;s doQuote(). This one dbl-quotes instead of sgl-quotes</p>\r\n\r\n	<p><strong>smd_pregPos</strong></p>\r\n\r\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>\r\n\r\n	<p><strong>array_combine</strong></p>\r\n\r\n	<p>PHP4 equivalent of the standard PHP5 function, lifted from php.net</p>\r\n\r\n	<p><strong>htmlspecialchars_decode</strong></p>\r\n\r\n	<p>PHP4 equivalent of the standard PHP5 function, lifted from php.net</p>\r\n\r\n</div>\n<!-- --- BEGIN PLUGIN CSS --- -->\n<style type=\"text/css\">\r\n#smd_help { line-height:1.3 ;}\r\n#smd_help code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #FFFFCC;}\r\n#smd_help code.block { font-weight:normal; border:1px dotted #999; background-color: #f0e68c; display:block; margin:10px 10px 20px; padding:10px; }\r\n#smd_help a:link, #smd_help a:visited { color: blue; text-decoration: none; border-bottom: 1px solid blue; padding-bottom:1px;}\r\n#smd_help a:hover, #smd_help a:active { color: blue; text-decoration: none; border-bottom: 2px solid blue; padding-bottom:1px;}\r\n#smd_help h1 { color: #369; font: 20px Georgia, sans-serif; margin: 0; text-align: center; }\r\n#smd_help h2 { border-bottom: 1px solid black; padding:10px 0 0; color: #369; font: 17px Georgia, sans-serif; }\r\n#smd_help h3 { color: #693; font: bold 12px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0;text-transform: uppercase;}\r\n#smd_help h4 { font: bold 11px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0 ;text-transform: uppercase; }\r\n#smd_help .att-name { font-weight:bold; }\r\n#smd_help .mand { background:#eee; border:1px dotted #999; }\r\n#smd_help table {width:100%; text-align:center; padding-bottom:1em;}\r\n#smd_help td, #smd_help th {border:1px solid #999; padding:.5em 0;}\r\n</style>\n<!-- --- END PLUGIN CSS --- -->','// 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\nclass smd_MLP {\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	function smd_MLP($plug, $prefx, $strarray, $lng=\'en-gb\', $ev=\'public\') {\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	function smd_Callback($event=\'l10n.enumerate_strings\', $step=\'\', $pre=0) {\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		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\nfunction smd_doList($lst, $rng=true, $sub=\"\", $qte=true, $dlm=\",\", $lax=true) {\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			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		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		$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				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			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	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\nfunction smd_split($str, $allowRange=true, $splitat=\"/(,|,\\s)+/\", $pregopt = PREG_SPLIT_NO_EMPTY) {\r\n	$retarr = array();\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	$elems = preg_split($pat, $str, -1, $pregopt);\r\n	foreach ($elems as $item) {\r\n		$item = trim($item);\r\n		$negate = false;\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		// 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			// 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			$retarr = array_merge($retarr, $rng);\r\n		} else {\r\n			$retarr[] = (($negate) ? \"!\" : \"\") . $item;\r\n		}\r\n	}\r\n	return $retarr;\r\n}\r\n}\r\n\r\nif (!function_exists(\"smd_doDblQuote\")) {\r\nfunction smd_doDblQuote($val) {\r\n	return \'\"\'.$val.\'\"\';\r\n}\r\n}\r\n\r\nif (!function_exists(\"smd_removeQSVar\")) {\r\nfunction smd_removeQSVar($url, $key) {\r\n	$url = preg_replace(\'/(.*)(\\?|&)\' . $key . \'=[^&]+?(&)(.*)/i\', \'$1$2$4\', $url . \'&\');\r\n	$url = substr($url, 0, -1);\r\n	return ($url);\r\n}\r\n}\r\n\r\nif (!function_exists(\"smd_addQSVar\")) {\r\nfunction smd_addQSVar($url, $key, $value) {\r\n	$url = smd_removeQSVar($url, $key);\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\nfunction smd_getSubCats($parent,$cattype) {\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\nfunction smd_getOpts($str, $allowed, $idprefix=\"\", $allowRange=true, $splitat=\"/(,|,\\s)+/\", $pregopt = PREG_SPLIT_NO_EMPTY) {\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	// 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		foreach ($opt as $var => $args) {\r\n			$opvar = ($args[4] == 1) ? \"out\" : \"notout\";\r\n			switch ($args[0]) {\r\n				case 1:\r\n					if (($thismatch === $var) && in_array($var,$allowed)) {\r\n						$matched = true;\r\n						if ($args[2] != \"\" && $args[3]) {\r\n							$rep = str_replace($var, $args[1].$args[2], $thismatch);\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					if ((is_int(smd_pregPos($pat, $thismatch, $fnd))) && in_array($var,$allowed)) {\r\n						$matched = true;\r\n						if ($args[2] != \"\" && $args[3]) {\r\n							$rep = str_replace($var, $args[1].$args[2], $thismatch);\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					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						if (($args[3]) && (isset($thisarticle[strtolower($fieldname)]))) {\r\n							$fieldContents = $thisarticle[strtolower($fieldname)];\r\n						} else {\r\n							$fieldContents = $fieldname;\r\n						}\r\n						if (!empty($fieldContents)) {\r\n							$subout = smd_split(strip_tags($fieldContents), $allowRange, $splitat, $pregopt);\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			if ($matched) {\r\n				break;\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	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	$FoundString = null;\r\n	if (preg_match($sPattern, $sSubject, $aMatches, PREG_OFFSET_CAPTURE, $iOffset) > 0) {\r\n		$FoundString = $aMatches[0][0];\r\n		return $aMatches[0][1];\r\n	} else {\r\n		return false;\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		$out = array();\r\n		foreach($arr1 as $key1 => $value1) {\r\n			$out[$value1] = $arr2[$key1];\r\n		}\r\n   	return $out;\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		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\r\n// of coupling and cohesion in these functions. I\'m warning you... it\'s not pretty\r\nif (!class_exists(\'smd_MLP\')) {\r\nclass smd_MLP {\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	function smd_MLP($plug, $prefx, $strarray, $lng=\'en-gb\', $ev=\'public\') {\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	function smd_Callback($event=\'l10n.enumerate_strings\', $step=\'\', $pre=0) {\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		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\nfunction smd_doList($lst, $rng=true, $sub=\"\", $qte=true, $dlm=\",\", $lax=true) {\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			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		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		$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				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			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	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\nfunction smd_split($str, $allowRange=true, $splitat=\"/(,|,\\s)+/\", $pregopt = PREG_SPLIT_NO_EMPTY) {\r\n	$retarr = array();\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	$elems = preg_split($pat, $str, -1, $pregopt);\r\n	foreach ($elems as $item) {\r\n		$item = trim($item);\r\n		$negate = false;\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		// 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			// 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			$retarr = array_merge($retarr, $rng);\r\n		} else {\r\n			$retarr[] = (($negate) ? \"!\" : \"\") . $item;\r\n		}\r\n	}\r\n	return $retarr;\r\n}\r\n}\r\n\r\nif (!function_exists(\"smd_doDblQuote\")) {\r\nfunction smd_doDblQuote($val) {\r\n	return \'\"\'.$val.\'\"\';\r\n}\r\n}\r\n\r\nif (!function_exists(\"smd_removeQSVar\")) {\r\nfunction smd_removeQSVar($url, $key) {\r\n	$url = preg_replace(\'/(.*)(\\?|&)\' . $key . \'=[^&]+?(&)(.*)/i\', \'$1$2$4\', $url . \'&\');\r\n	$url = substr($url, 0, -1);\r\n	return ($url);\r\n}\r\n}\r\n\r\nif (!function_exists(\"smd_addQSVar\")) {\r\nfunction smd_addQSVar($url, $key, $value) {\r\n	$url = smd_removeQSVar($url, $key);\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\nfunction smd_getSubCats($parent,$cattype) {\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\nfunction smd_getOpts($str, $allowed, $idprefix=\"\", $allowRange=true, $splitat=\"/(,|,\\s)+/\", $pregopt = PREG_SPLIT_NO_EMPTY) {\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	// 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		foreach ($opt as $var => $args) {\r\n			$opvar = ($args[4] == 1) ? \"out\" : \"notout\";\r\n			switch ($args[0]) {\r\n				case 1:\r\n					if (($thismatch === $var) && in_array($var,$allowed)) {\r\n						$matched = true;\r\n						if ($args[2] != \"\" && $args[3]) {\r\n							$rep = str_replace($var, $args[1].$args[2], $thismatch);\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					if ((is_int(smd_pregPos($pat, $thismatch, $fnd))) && in_array($var,$allowed)) {\r\n						$matched = true;\r\n						if ($args[2] != \"\" && $args[3]) {\r\n							$rep = str_replace($var, $args[1].$args[2], $thismatch);\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					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						if (($args[3]) && (isset($thisarticle[strtolower($fieldname)]))) {\r\n							$fieldContents = $thisarticle[strtolower($fieldname)];\r\n						} else {\r\n							$fieldContents = $fieldname;\r\n						}\r\n						if (!empty($fieldContents)) {\r\n							$subout = smd_split(strip_tags($fieldContents), $allowRange, $splitat, $pregopt);\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			if ($matched) {\r\n				break;\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	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	$FoundString = null;\r\n	if (preg_match($sPattern, $sSubject, $aMatches, PREG_OFFSET_CAPTURE, $iOffset) > 0) {\r\n		$FoundString = $aMatches[0][0];\r\n		return $aMatches[0][1];\r\n	} else {\r\n		return false;\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		$out = array();\r\n		foreach($arr1 as $key1 => $value1) {\r\n			$out[$value1] = $arr2[$key1];\r\n		}\r\n   	return $out;\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		return strtr($string, array_flip(get_html_translation_table(HTML_SPECIALCHARS, $quote_style)));\r\n	}\r\n}','c2da3c9dcce2ec98749fe95b47e8475e',2,5,0),('soo_if_frontpage',1,'Jeff Soo','http://ipsedixit.net/txp/','0.1.8','Check if page is a section front page','<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_if_frontpage</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=\"#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>Similar to the <code>glx_if_frontpage</code> tag from the no-longer-supported <a href=\"http://www.markupartist.com/files/glx_if.txt\"><code>glx_if</code> plugin</a>. Most of <code>glx_if</code>&#8217;s functionality is now available through core Txp tags, but not this (well, not without an absurd stack of conditional tags). To <code>glx_if_frontpage</code> it adds <code>txp:else /&gt;</code> compatibility, and a couple of attributes for more control.</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;\n</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;\n</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;\n</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:\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>\n	</ul></p>\n\n	<h2 id=\"history\">Version History</h2>\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 </div>','\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	return parse(EvalElse($thing,\n		( $section == \'*\' or in_list($s, $section) ) and\n		( ! $atts[\'pg\'] or $pg < 2 ) and\n		! ( $c or $q or $author or $month or $id or $p )\n	));\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	return parse(EvalElse($thing,\n		( $section == \'*\' or in_list($s, $section) ) and\n		( ! $atts[\'pg\'] or $pg < 2 ) and\n		! ( $c or $q or $author or $month or $id or $p )\n	));\n}\n','e18f5ea2496454bf39da325064e89fef',0,5,0),('upm_textile',1,'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),('arc_twitter',1,'Andy Carter','http://andy-carter.com/','4.2.1','<a href=\"http://www.twitter.com\">Twitter</a> for Textpattern','	<h1 class=\"title\"><span class=\"caps\">TXP</span> Tweet (arc_twitter for Textpattern)</h1>\n\n	<ol>\n		<li><a href=\"#arc_twitter_author\">Author</a></li>\n		<li><a href=\"#arc_twitter_installation\">Installation / Uninstallation</a></li>\n		<li><a href=\"#arc_twitter_tag\">The arc_twitter tag</a></li>\n		<li><a href=\"#arc_twitter_search_tag\">The arc_twitter_search tag</a></li>\n		<li><a href=\"#arc_twitter_tweet_button_tag\">The arc_twitter_tweet_button tag</a></li>\n		<li><a href=\"#arc_twitter_follow_button_tag\">The arc_twitter_follow_button tag</a></li>\n		<li><a href=\"#arc_twitter_tweet_url\">The arc_twitter_tweet_url tag</a></li>\n		<li><a href=\"#arc_twitter_tinyurl\">The arc_twitter_tinyurl tag</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	</ol>\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	<p>Requirements:-</p>\n\n	<ul>\n		<li>Textpattern 4.5+</li>\n		<li><span class=\"caps\">PHP</span> 5 and cURL</li>\n	</ul>\n\n	<h2 class=\"section\" 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	<h2 class=\"section\" 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><span class=\"tag\">arc_twitter</span> should now be ready for use on the public-side of your site.</p>\n\n	<p>To unlock the admin features of this plugin 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 class=\"section\" id=\"arc_twitter_tag\">The arc_twitter tag</h2>\n\n	<h3>Syntax</h3>\n\n	<p>&lt;txp:arc_twitter user=&quot;drmonkeyninja&quot; /&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</td>\n			<td> <em>arc_twitter username</em></td>\n			<td>user=&quot;drmonkeyninja&quot;</td>\n		</tr>\n		<tr>\n			<td>retweets</td>\n			<td>1 to include retweets, 0 to exclude them from the feed</td>\n			<td>0</td>\n		</tr>\n		<tr>\n			<td>replies</td>\n			<td>1 to include replies, 0 to exclude them from the feed</td>\n			<td>1</td>\n		</tr>\n		<tr>\n			<td>limit</td>\n			<td>Maximum number of tweets to display (max. 200)</td>\n			<td>10</td>\n			<td>limit=&quot;25&quot;</td>\n		</tr>\n		<tr>\n			<td>fetch</td>\n			<td>Maximum number of tweets to retrieve from Twitter (max. 200)<sup class=\"footnote\"><a href=\"#fn209229727352a0a74923850\">1</a></sup></td>\n			<td>0</td>\n			<td>fetch=&quot;50&quot;</td>\n		</tr>\n		<tr>\n			<td>full_urls</td>\n			<td>1 to display full <span class=\"caps\"><span class=\"caps\">URL</span></span>s, 0 for t.co <span class=\"caps\"><span class=\"caps\">URL</span></span>s</td>\n			<td>0</td>\n		</tr>\n		<tr>\n			<td>dateformat</td>\n			<td>Format that update dates will appear as</td>\n			<td> <em>Archive date format</em></td>\n			<td>dateformat=&quot;%b %Oe, %I:%M %p&quot;</td>\n		</tr>\n		<tr>\n			<td>label</td>\n			<td>Label for the top of the list</td>\n			<td> <em>no label</em></td>\n			<td>label=&quot;My Twitter timeline&quot;</td>\n		</tr>\n		<tr>\n			<td>labeltag</td>\n			<td>Independent wraptag for label</td>\n			<td> <em>unset</em></td>\n			<td>labeltag=&quot;h3&quot;</td>\n		</tr>\n		<tr>\n			<td>break</td>\n			<td><span class=\"caps\"><span class=\"caps\">HTML</span></span> tag (without brackets), or string, used to separate the updates</td>\n			<td>li</td>\n			<td>break=&quot;br&quot;</td>\n		</tr>\n		<tr>\n			<td>wraptag</td>\n			<td><span class=\"caps\"><span class=\"caps\">HTML</span></span> tag to be used as the wraptag, without brackets</td>\n			<td> <em>unset</em></td>\n			<td>wraptag=&quot;ul&quot;</td>\n		</tr>\n		<tr>\n			<td>class</td>\n			<td><span class=\"caps\"><span class=\"caps\">CSS</span></span> class attribute for wraptag</td>\n			<td>arc_twitter</td>\n			<td>class=&quot;twitter&quot;</td>\n		</tr>\n		<tr>\n			<td>class_posted</td>\n			<td><span class=\"caps\"><span class=\"caps\">CSS</span></span> class attribute applied to span tag around posted date</td>\n			<td>arc_twitter-posted</td>\n		</tr>\n	</table>\n\n	<p id=\"fn209229727352a0a74923850\" 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	<h3>Example usage</h3>\n\n	<p>&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;</p>\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	<h2 class=\"section\" id=\"arc_twitter_search_tag\">The arc_twitter_search tag</h2>\n\n	<h3>Syntax</h3>\n\n	<p>&lt;txp:arc_twitter_search hashtags=&quot;txp&quot; /&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>search</td>\n			<td>Comma separated list of search words</td>\n			<td> <em>unset</em></td>\n			<td>search=&quot;txp,textpattern&quot;</td>\n		</tr>\n		<tr>\n			<td>hashtags</td>\n			<td>Comma separated list of hashtags to search for (not including the hash)</td>\n			<td> <em>unset</em></td>\n			<td>hashtags=&quot;txp,textpattern&quot;</td>\n		</tr>\n		<tr>\n			<td>reply</td>\n			<td>Username of tweets in reply to</td>\n			<td> <em>unset</em></td>\n			<td>reply=&quot;twitter&quot;</td>\n		</tr>\n		<tr>\n			<td>mention</td>\n			<td>Username of user mentioned in tweets (<i>i.e.</i> tweets containing @username)</td>\n			<td> <em>unset</em></td>\n			<td>mention=&quot;twitter&quot;</td>\n		</tr>\n		<tr>\n			<td>limit</td>\n			<td>Maximum number of tweets to display (max. 200)</td>\n			<td>10</td>\n			<td>limit=&quot;25&quot;</td>\n		</tr>\n		<tr>\n			<td>full_urls</td>\n			<td>1 to display full <span class=\"caps\"><span class=\"caps\">URL</span></span>s, 0 for t.co <span class=\"caps\"><span class=\"caps\">URL</span></span>s</td>\n			<td>0</td>\n		</tr>\n		<tr>\n			<td>dateformat</td>\n			<td>Format that update dates will appear as</td>\n			<td> <em>Archive date format</em></td>\n			<td>dateformat=&quot;%b %Oe, %I:%M %p&quot;</td>\n		</tr>\n		<tr>\n			<td>label</td>\n			<td>Label for the top of the list</td>\n			<td> <em>no label</em></td>\n		</tr>\n		<tr>\n			<td>labeltag</td>\n			<td>Independent wraptag for label</td>\n			<td> <em>unset</em></td>\n		</tr>\n		<tr>\n			<td>break</td>\n			<td><span class=\"caps\"><span class=\"caps\">HTML</span></span> tag (without brackets), or string, used to separate the updates</td>\n			<td>li</td>\n		</tr>\n		<tr>\n			<td>wraptag</td>\n			<td><span class=\"caps\"><span class=\"caps\">HTML</span></span> tag to be used as the wraptag, without brackets</td>\n			<td> <em>unset</em></td>\n		</tr>\n		<tr>\n			<td>class</td>\n			<td><span class=\"caps\"><span class=\"caps\">CSS</span></span> class attribute for wraptag</td>\n			<td>arc_twitter_search</td>\n		</tr>\n		<tr>\n			<td>class_user</td>\n			<td><span class=\"caps\"><span class=\"caps\">CSS</span></span> class attribute applied to span tag around user name</td>\n			<td>arc_twitter-user</td>\n		</tr>\n		<tr>\n			<td>class_posted</td>\n			<td><span class=\"caps\"><span class=\"caps\">CSS</span></span> class attribute applied to span tag around posted date</td>\n			<td>arc_twitter-posted</td>\n		</tr>\n	</table>\n\n	<h3>Example usage</h3>\n\n	<p>&lt;txp:arc_twitter_search search=&quot;plugin&quot; hashtags=&quot;txp,textpattern&quot; limit=&quot;25&quot; /&gt;</p>\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	<h2 class=\"section\" id=\"arc_twitter_retweet_tag\">The arc_twitter_tweet_button tag</h2>\n\n	<p>Outputs the Twitter tweet button (formerly &lt;txp:arc_twitter_retweet /&gt;).</p>\n\n	<h3>Syntax</h3>\n\n	<p>&lt;txp:arc_twitter_tweet_button /&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 quote</td>\n			<td> <em>arc_twitter username</em></td>\n			<td>user=&quot;drmonkeyninja&quot;</td>\n		</tr>\n		<tr>\n			<td>url</td>\n			<td><span class=\"caps\"><span class=\"caps\">URL</span></span> to tweet</td>\n		</tr>\n		<tr>\n			<td>text</td>\n			<td>Tweet text</td>\n		</tr>\n		<tr>\n			<td>follow1</td>\n			<td>Suggested Twitter account to follow, for example your own</td>\n			<td>A Twitter user to recommend</td>\n			<td>follow1=&quot;Textpattern&quot;</td>\n		</tr>\n		<tr>\n			<td>follow2</td>\n			<td>As follow1</td>\n			<td> <em>unset</em></td>\n		</tr>\n		<tr>\n			<td>lang</td>\n			<td>Language</td>\n			<td>en</td>\n			<td>lang=&quot;es&quot;</td>\n		</tr>\n		<tr>\n			<td>count</td>\n			<td>Count box position, options: none, horizontal or vertical</td>\n			<td>horizontal</td>\n			<td>count=&quot;none&quot;</td>\n		</tr>\n		<tr>\n			<td>include_js</td>\n			<td>Whether or not to include the widget JavaScript</td>\n			<td>1</td>\n			<td>include_js=&quot;0&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>0</td>\n			<td>optimise_js=&quot;1&quot;</td>\n		</tr>\n		<tr>\n			<td>class</td>\n			<td><span class=\"caps\"><span class=\"caps\">CSS</span></span> class attribute applied to the tweet button</td>\n			<td>twitter-share-button</td>\n		</tr>\n	</table>\n\n	<h2 class=\"section\" id=\"arc_twitter_follow_button_tag\">The arc_twitter_follow_button tag</h2>\n\n	<p>Outputs the Twitter follow button (an alternative version is included in arc_twitter_intents).</p>\n\n	<h3>Syntax</h3>\n\n	<p>&lt;txp:arc_twitter_follow_button /&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>lang</td>\n			<td>Language</td>\n			<td>en</td>\n			<td>lang=&quot;es&quot;</td>\n		</tr>\n		<tr>\n			<td>count</td>\n			<td>Whether or not the follower count is shown</td>\n			<td>1</td>\n			<td>count=&quot;0&quot;</td>\n		</tr>\n		<tr>\n			<td>include_js</td>\n			<td>Whether or not to include the widget JavaScript</td>\n			<td>1</td>\n			<td>include_js=&quot;0&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>0</td>\n			<td>optimise_js=&quot;1&quot;</td>\n		</tr>\n		<tr>\n			<td>class</td>\n			<td><span class=\"caps\"><span class=\"caps\">CSS</span></span> class attribute applied to the follow button</td>\n			<td>twitter-follow-button</td>\n		</tr>\n	</table>\n\n	<h2 class=\"section\" id=\"arc_twitter_tweet_url\">arc_twitter_tweet_url</h2>\n\n	<p>Returns the <span class=\"caps\">URL</span> of the Twitter status for an article.</p>\n\n	<h3>Syntax</h3>\n\n	<p>&lt;txp:arc_twitter_tweet_url /&gt;</p>\n\n	<p>&lt;txp:arc_twitter_tweet_url&gt;Link text&lt;/txp:arc_twitter_tweet_url&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>Textpattern article ID</td>\n			<td> <em>current article</em></td>\n			<td>id=&quot;1&quot;</td>\n		</tr>\n		<tr>\n			<td>title</td>\n			<td>Title attribute of the link</td>\n			<td> <em>unset</em></td>\n		</tr>\n		<tr>\n			<td>class</td>\n			<td><span class=\"caps\"><span class=\"caps\">CSS</span></span> class attribute applied to the link</td>\n			<td> <em>unset</em></td>\n		</tr>\n	</table>\n\n	<h2 class=\"section\" id=\"arc_twitter_tinyurl\">arc_twitter_tinyurl</h2>\n\n	<p>Returns the shortened <span class=\"caps\">URL</span> of the article used for the Twitter update.</p>\n\n	<h3>Syntax</h3>\n\n	<p>&lt;txp:arc_twitter_tinyurl /&gt;</p>\n\n	<p>&lt;txp:arc_twitter_tinyurl&gt;Link text&lt;/txp:arc_twitter_tinyurl&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>Textpattern article ID</td>\n			<td> <em>current article</em></td>\n			<td>id=&quot;1&quot;</td>\n		</tr>\n		<tr>\n			<td>title</td>\n			<td>Title attribute of the link</td>\n			<td> <em>unset</em></td>\n		</tr>\n		<tr>\n			<td>class</td>\n			<td><span class=\"caps\"><span class=\"caps\">CSS</span></span> class attribute applied to the link</td>\n			<td> <em>unset</em></td>\n		</tr>\n	</table>\n\n	<h2 class=\"section\" 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	<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>caching</td>\n			<td>&#8216;1&#8217; to cache feed, &#8216;0&#8217; to turn caching off (not recommended)</td>\n			<td>1</td>\n			<td>caching=&quot;1&quot;</td>\n		</tr>\n		<tr>\n			<td>cache_dir</td>\n			<td>Absolute path to the cache directory (must be writable)</td>\n			<td> <em>arc_twitter preferences</em></td>\n		</tr>\n		<tr>\n			<td>cache_time</td>\n			<td>Time in minutes that the cache files are stored before being refreshed</td>\n			<td>5</td>\n			<td>cache_time=&quot;30&quot;</td>\n		</tr>\n	</table>\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 class=\"section\" 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 class=\"section\" 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	<h2 class=\"section\" 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>','global $prefs,$txpcfg,$arc_twitter_consumerKey,$arc_twitter_consumerSecret;\n\n$arc_twitter = array();\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\');\nif (!isset($prefs[\'arc_twitter_prefix\']))\n  set_pref(\'arc_twitter_prefix\',\'Just posted:\', \'arc_twitter\', 2, \'text_input\');\nif (!isset($prefs[\'arc_twitter_suffix\']))\n  set_pref(\'arc_twitter_suffix\',\'\', \'arc_twitter\', 2, \'text_input\');\nif (!isset($prefs[\'arc_twitter_cache_dir\']))\n  set_pref(\'arc_twitter_cache_dir\',$txpcfg[\'txpath\'].$prefs[\'tempdir\'], \'arc_twitter\', 1, \'text_input\');\nif (!isset($prefs[\'arc_twitter_tweet_default\']))\n  set_pref(\'arc_twitter_tweet_default\', 1, \'arc_twitter\', 2, \'yesnoRadio\');\nif (!isset($prefs[\'arc_twitter_url_method\']))\n  set_pref(\'arc_twitter_url_method\', \'full\', \'arc_twitter\', 2,\n	\'arc_twitter_url_method_select\');\nif (!isset($prefs[\'arc_short_url\']))\n  set_pref(\'arc_short_url\', 0, \'arc_twitter\', 2, \'yesnoRadio\');\nif (!isset($prefs[\'arc_short_site_url\']))\n  set_pref(\'arc_short_site_url\', $prefs[\'siteurl\'], \'arc_twitter\', 2, \'text_input\');\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}\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\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(140),\n		tinyurl VARCHAR(30));\";\n\n	if (!safe_query($sql)) {\n		return \'Error - unable to create arc_twitter table\';\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\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		$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		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) $short_site_url = $prefs[\'siteurl\'];\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	}\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		\'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<=140 && $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\' : \'140\',\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 = 140-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		$content = tag(arc_Twitter::makeLinks($rs2[\'tweet\']),\'p\');\n		return $data.fieldset($content, \'Twitter update\', \'arc_twitter\');\n	} else {\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\"\')\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\"\')\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			// Make short URL\n			$url = permlinkurl($article);\n			$short_url = arc_shorten_url($url,$prefs[\'arc_twitter_url_method\'],\n				array(\'id\'=>$article_id));\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)>140) {\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  switch ($method) {\n	case \'smd\': // create a shortened URL using SMD Short URL\n		return ($atts[\'id\']) ? hu.$atts[\'id\'] : false; break;\n	case \'arc_twitter\': // native URL shortening\n\n		return ($atts[\'id\']) ? PROTOCOL.$prefs[\'arc_short_site_url\'].\'/\'.$atts[\'id\'] : false;\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+\\.]+)\\b/i\' => \"<a href=\'$0\' rel=\'external\'>$0</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$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\');\nif (!isset($prefs[\'arc_twitter_prefix\']))\n  set_pref(\'arc_twitter_prefix\',\'Just posted:\', \'arc_twitter\', 2, \'text_input\');\nif (!isset($prefs[\'arc_twitter_suffix\']))\n  set_pref(\'arc_twitter_suffix\',\'\', \'arc_twitter\', 2, \'text_input\');\nif (!isset($prefs[\'arc_twitter_cache_dir\']))\n  set_pref(\'arc_twitter_cache_dir\',$txpcfg[\'txpath\'].$prefs[\'tempdir\'], \'arc_twitter\', 1, \'text_input\');\nif (!isset($prefs[\'arc_twitter_tweet_default\']))\n  set_pref(\'arc_twitter_tweet_default\', 1, \'arc_twitter\', 2, \'yesnoRadio\');\nif (!isset($prefs[\'arc_twitter_url_method\']))\n  set_pref(\'arc_twitter_url_method\', \'full\', \'arc_twitter\', 2,\n	\'arc_twitter_url_method_select\');\nif (!isset($prefs[\'arc_short_url\']))\n  set_pref(\'arc_short_url\', 0, \'arc_twitter\', 2, \'yesnoRadio\');\nif (!isset($prefs[\'arc_short_site_url\']))\n  set_pref(\'arc_short_site_url\', $prefs[\'siteurl\'], \'arc_twitter\', 2, \'text_input\');\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}\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\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(140),\n		tinyurl VARCHAR(30));\";\n\n	if (!safe_query($sql)) {\n		return \'Error - unable to create arc_twitter table\';\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\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		$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		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) $short_site_url = $prefs[\'siteurl\'];\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	}\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		\'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<=140 && $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\' : \'140\',\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 = 140-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		$content = tag(arc_Twitter::makeLinks($rs2[\'tweet\']),\'p\');\n		return $data.fieldset($content, \'Twitter update\', \'arc_twitter\');\n	} else {\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\"\')\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\"\')\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			// Make short URL\n			$url = permlinkurl($article);\n			$short_url = arc_shorten_url($url,$prefs[\'arc_twitter_url_method\'],\n				array(\'id\'=>$article_id));\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)>140) {\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  switch ($method) {\n	case \'smd\': // create a shortened URL using SMD Short URL\n		return ($atts[\'id\']) ? hu.$atts[\'id\'] : false; break;\n	case \'arc_twitter\': // native URL shortening\n\n		return ($atts[\'id\']) ? PROTOCOL.$prefs[\'arc_short_site_url\'].\'/\'.$atts[\'id\'] : false;\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+\\.]+)\\b/i\' => \"<a href=\'$0\' rel=\'external\'>$0</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}','2024019beb089d05ab0396da67e69863',3,5,3),('smd_thumbnail',0,'Stef Dawson','http://stefdawson.com/','0.31','Multiple image thumbnails of arbitrary dimensions','<h1>smd_thumbnail</h1>\n\n<h2>Features</h2>\n\n	<ul>\n		<li>Assign any number of thumbnails to Txp&#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 Txp&#8217;s thumbs if you wish</li>\n		<li>Conditional thumbnail check available</li>\n	</ul>\n\n<h2>Installation / upgrading / uninstallation</h2>\n\n<p class=\"warning\">Requires Txp 4.5.0+</p>\n\n<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/1186/smd_thumbnail\">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=34367\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n<p class=\"warning\">When upgrading froim v0.1x to v0.20, your preferences for <em>Keep Txp thumbnails in sync with default profile</em> will be removed. Please visit the <em>Tools/Prefs</em> area to reinstate the behaviour you desire.</p>\n\n<p>To uninstall, delete from the Admin -&gt; Plugins page.</p>\n\n<h2>Usage &#8212; admin side</h2>\n\n<p>Visit the Content-&gt;Images tab. Above the list of images is an area labelled <em>Thumbnail Profiles</em>. Click it to reveal the control panel. You can toggle this panel on and off by clicking the heading.</p>\n\n<p>You must define at least one profile to begin with, so click the <em>New profile</em> 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>Width</strong>: thumbnail width, in pixels. If left blank (or set to 0) 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 0) 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 jpg or png(24) images. The higher the value the better the quality and the bigger the file size. Default: 75</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 Images 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 Txp&#8217;s thumbs</a> is on, it will also become Txp&#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 Save 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, hit the <em>[Delete]</em> 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.</p>\n\n<h3 id=\"smd_thumb_create\">Creating thumbnails</h3>\n\n<p>Pick one of your images and click to edit it.</p>\n\n<p>Beneath the main picture you will see an area labelled <em>Thumbnails</em>. You can toggle this panel on and off by clicking the heading.</p>\n\n<p>Inside the panel is a simple select list containing one entry for each thumbnail profile you have configured, plus the special entry <em>All sizes</em>. You can select one of these entries and click <em>Create</em> to create thumbnail(s) in the selected size or hit <em>Delete</em> to remove the selected thumbnail(s).</p>\n\n<p>Once created, the thumbnails appear in the panel; remember they can be tucked away if you don&#8217;t want to look at them.</p>\n\n<p>If you select <em>All sizes</em> and hit <em>Create</em>, you will get one thumbnail for each active profile. Similarly, if you hit <em>Delete</em> 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.</p>\n\n<p>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 <em>do not</em> 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 Txp&#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 id=\"smd_thumb_custom\">Custom thumbnails</h3>\n\n<p>If you prefer to make your own thumbs in an external program, you can still set up the profiles as normal but instead of clicking <em>Create</em> in the Image Edit screen, you can pick a profile size from the dropdown and use the file picker facility (Browse&#8230; / Choose&#8230;) to select a thumbnail from your hard drive. When you click <em>Upload</em> 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 &#8212; even ones that have been auto-created. Some things to note about this feature:</p>\n\n	<ul>\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 Browse for an image (the file upload box is greyed out) unless you have chosen one profile</li>\n	</ul>\n\n<h3 id=\"smd_thumb_batch\">Batch thumbnail creation</h3>\n\n<p>You can also create thumbnails en-masse. Click the <em>Tools</em> button (labelled &#8216;<em>Tools / Prefs</em>&#8217; if you have sufficient rights) in the top-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	<ul>\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	</ul>\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), hit <em>Create</em>. Once you confirm your action, the plugin will go away and 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. Note 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 &#8212; especially with large image pools &#8212; the process is done as a background task via <span class=\"caps\">AJAX</span>. After clicking the <em>Create</em> 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 Images tab (e.g. click to the <em>Profiles</em> pane) you will see your chosen default thumbnails in the list &#8212; assuming you&#8217;ve set one as default.</p>\n\n<h3 id=\"smd_thumb_prefs\">Preferences</h3>\n\n<p>If you have sufficient privileges, in the lower portion of the <em>Tools</em> 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 (Txp) thumbnail. If you have elected not to have Txp thumbnails created then it&#8217;ll probably not work very well :-)</p>\n\n<p>This preference applies:</p>\n\n	<ul>\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 <em>Recreate thumbnails on re-upload of main image</em> is checked</li>\n	</ul>\n\n<h4>Keep Txp thumbnails in sync with default profile</h4>\n\n<p>(This option only appears if you have set a profile as default)</p>\n\n<p>Ordinarily, smd_thumbnail works independently of Txp&#8217;s thumbs. 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.</p>\n\n<p>But if you start creating thumbs with the plugin you will notice that Txp&#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 Txp 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 Txp&#8217;s thumbnails. This has a few effects worth highlighting.</p>\n\n<p>When the <em>Creation</em> checkbox is ticked:</p>\n\n	<ol>\n		<li>if you batch create thumbs, upload a new image, or replace an existing image, a new Txp thumbnail will be created at the default size. Any existing thumbs will be overwritten</li>\n		<li>if you change default profile, any Txp 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>When the <em>Deletion</em> checkbox is ticked:</p>\n\n	<ol>\n		<li>if you delete a single thumb that corresponds to the default profile, both the profile thumb and Txp&#8217;s thumb will be deleted</li>\n		<li>if you delete an entire profile, all its thumbnails <strong>and all Txp&#8217;s thumbs</strong> will be removed</li>\n	</ol>\n\n<p>Further:</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 <em>Deletion</em> 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 Image Edit screen) any thumbnails that have already been assigned to the image are left as they are &#8212; 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>Note one important exception: Txp thumbnails are <strong><span class=\"caps\">NOT</span></strong> covered by this option &#8212; they are governed by the <em>Keep Txp thumbs in sync</em>&#8217;s &#8216;Creation&#8217; checkbox. Thus, if you upload a replacement image and the &#8216;Creation&#8217; checkbox is on, you will replace Txp&#8217;s thumb regardless of the setting of the <em>Recreate thumbnails&#8230;</em> option.</p>\n\n<h2 id=\"smd_thumbnail\">Tag: <code>&lt;txp:smd_thumbnail /&gt;</code></h2>\n\n<p>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 these exceptions:</p>\n\n	<ul>\n		<li><span class=\"atnm\">type</span> : use 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		<li><span class=\"atnm\">class</span> : if omitted, the name of the profile will be used as a class name for the img tag. If you specify a wraptag and omit the class, the profile name will be used as a class on both the container and the img tag</li>\n		<li><span class=\"atnm\">align</span> : this attribute has been removed</li>\n		<li><span class=\"atnm\">add_stamp</span> : adds 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 thumbs properly thus increasing bandwidth usage. Default: 0</li>\n		<li><span class=\"atnm\">force_size</span> : usually when you set one or other width/height to 0 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>&lt;img&gt;</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><span class=\"atnm\">display</span> : by 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><span class=\"atnm\">form</span> : you 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	</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<h2 id=\"smd_thumbnail_info\">Tag: <code>&lt;txp:smd_thumbnail_info /&gt;</code></h2>\n\n<p>Display various information from the current <code>&lt;txp:smd_thumbnail&gt;</code> tag (in either its form or container). Attributes:</p>\n\n	<ul>\n		<li><span class=\"atnm\">item</span> : list of things you wish to display. Choose from:\n		<ul>\n			<li><code>url</code> : full image <span class=\"caps\">URL</span> of the thumbnail</li>\n			<li><code>type</code> : thumbnail profile name</li>\n			<li><code>name</code> : image name</li>\n			<li><code>id</code> : thumbnail ID</li>\n			<li><code>ext</code> : thumbnail file extension</li>\n			<li><code>category</code> : image category name</li>\n			<li><code>category_title</code> : image category title</li>\n			<li><code>alt</code> : image alt text</li>\n			<li><code>title</code> : image title / caption</li>\n			<li><code>author</code> : image author</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>w</code> : thumbnail width (pixels)</li>\n			<li><code>h</code> : thumbnail height (pixels)</li>\n			<li><code>html_w</code> : <span class=\"caps\">HTML</span>-formatted width attribute</li>\n			<li><code>html_h</code> : <span class=\"caps\">HTML</span>-formatted height attribute</li>\n		</ul></li>\n		<li><span class=\"atnm\">wraptag</span> : <span class=\"caps\">HTML</span> tag, without angle brackets, to wrap all the items with. e.g. <code>wraptag=&quot;ul&quot;</code></li>\n		<li><span class=\"atnm\">class</span> : <span class=\"caps\">CSS</span> class name to apply to the wraptag</li>\n		<li><span class=\"atnm\">break</span> : <span class=\"caps\">HTML</span> tag, without angle brackets, to wrap each item with. e.g. <code>break=&quot;li&quot;</code></li>\n	</ul>\n\n<h2 id=\"smd_if_thumbnail\">Tag: <code>&lt;txp:smd_if_thumbnail&gt;</code></h2>\n\n<p>A direct replacement for the built in <code>&lt;txp:if_thumbnail /&gt;</code> tag with exactly the same functionality. It has one attribute:</p>\n\n	<ul>\n		<li><span class=\"atnm\">type</span> : 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<h2 id=\"smd_thumb_how_it_works\">How it works</h2>\n\n<p>For reference, when you create a profile a directory is created with that name inside your Txp <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 Txp 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 &#8212; use the <em>[Delete]</em> buttons in the control panel for that.</p>\n\n<h2>Author / credits</h2>\n\n<p>Written by <a href=\"http://stefdawson.com/contact\">Stef Dawson</a>. Many thanks to the beta test crew who offered feature and workflow advice, especially thebombsite, jakob, jstubbs and maniqui.</p>\n\n<h2 id=\"smd_thumb_changelog\">Changelog</h2>\n\n	<ul>\n		<li>26 Sep 2014 | 0.31 | Selected thumbnail creation for 4.6.x (thanks jpdupont)</li>\n		<li>14 Jan 2013 | 0.30 | For Txp 4.5.0 ; added mem_moderation_image support ; smd_thumbnail tag has form/container support ; added smd_thumbnail_info tag ; fixed image &lt;-&gt; select list interaction (damn you attr/prop!) ; added better tooltip on thumb hover ; fixed invalid markup ; added width &amp; height attributes (thanks jstubbs)</li>\n		<li>12 Oct 2011 | 0.22 | Added <code>display</code> attribute ; added &#8216;create from&#8217; preference (thanks the blue dragon/maniqui) ; fixed plugin for dashboard use ; fixed jQuery attr/prop in prefs ; fixed Txp menu interference (thanks maniqui)</li>\n		<li>12 May 2011 | 0.21 | Added <code>smd_thumb_get_profiles()</code> for other plugins to find which profiles are available</li>\n		<li>26 Jan 2011 | 0.20 | Tools / prefs separated from profile list ; permitted batch creation via 1) selected images 2) category 3) author (thanks jpdupont) ; added smd_thumb_auto_replace option and altered default behaviour (thanks maniqui) ; fixed Txp thumbnail creation on thumbnail replace and made prefs global &#8212; and only available to site admins (all thanks jstubbs) ; fixed New Profile button wrapping (thanks jpdupont)</li>\n		<li>21 Nov 2010 | 0.14 | Permitted thumbs to be larger than the original images (thanks maniqui) ; fixed callbacks for image uploading and deleting</li>\n		<li>10 Nov 2010 | 0.13 | Added confirmation on <em>Create all</em> ; relabelled upload box (thank uli) ; tamed styles ; silenced chmod warnings (thanks zero)</li>\n		<li>10 Nov 2010 | 0.12 | Added ability to sync default profile thumbs with the corresponding Txp thumbnails (thanks zero)</li>\n		<li>09 Nov 2010 | 0.11 | Added <code>force_size</code> and <code>add_stamp</code> (both thanks zero) ; removed unnecessary DB call</li>\n		<li>04 Oct 2010 | 0.10 | Official public release</li>\n		<li>06 Aug 2010 | 0.10beta | Initial beta release</li>\n	</ul>','/**\r\n * smd_thumbnail\r\n *\r\n * A Textpattern CMS plugin for managing multiple image thumbnails:\r\n *  -> Create unlimited thumbnail profiles of differing sizes for various site uses\r\n *  -> Thumbnail files are created in your image directory -- no realtime scaling\r\n *  -> Batch create / alter thumbnails\r\n *  -> Choose to sync the thumbnails with Textpattern\'s own thumbs\r\n *\r\n * @author Stef Dawson\r\n * @link   http://stefdawson.com/\r\n * TODO: Make Selections (lines 912-916) optional via prefs or something\r\n * TODO: Simplify AJAX response packets to speed things up\r\n * TODO: Interface elements for Profile creation don\'t get rendered if there are no images in pool\r\n */\r\nif (@txpinterface == \'admin\') {\r\n	global $smd_thumb_event, $smd_thumb_prevs;\r\n\r\n	$smd_thumb_event = \'smd_thumbnail\';\r\n	$smd_thumb_privs = \'1,2,3\'; // Plugin privs\r\n	$smd_thumb_prevs = array(\'1\'); // Privs for prefs\r\n\r\n	add_privs($smd_thumb_event, $smd_thumb_privs);\r\n	add_privs(\'smd_thumb_profiles\', $smd_thumb_privs);\r\n	register_callback(\'smd_thumb_welcome\', \'plugin_lifecycle.\'.$smd_thumb_event);\r\n	register_callback(\'smd_thumb_profiles\', \'image_ui\', \'extend_controls\');\r\n	register_callback(\'smd_thumbs\', \'image_ui\', \'thumbnail\');\r\n	register_callback(\'smd_thumb_edit\', \'image_ui\', \'thumbnail_edit\');\r\n	register_callback(\'smd_thumb_empty\', \'image_ui\', \'thumbnail_image\');\r\n	register_callback(\'smd_thumb_empty\', \'image_ui\', \'thumbnail_create\');\r\n	register_callback(\'smd_thumb_generate\', \'image_uploaded\', \'image\');\r\n	register_callback(\'smd_thumb_generate\', \'image_uploaded\', \'moderate\');\r\n	register_callback(\'smd_thumb_delete\', \'image_deleted\', \'image\');\r\n	register_callback(\'smd_thumb_create_one\', \'image\', \'smd_thumb_create_one\');\r\n	register_callback(\'smd_thumb_switch_active\', \'image\', \'smd_thumb_switch_active\');\r\n	register_callback(\'smd_thumb_switch_pref\', \'image\', \'smd_thumb_switch_pref\');\r\n	register_callback(\'smd_thumb_insert\', \'image\', \'smd_thumbnail_insert\', 1);\r\n	register_callback(\'smd_thumb_inject_css\', \'admin_side\', \'head_end\');\r\n} else {\r\n	smd_thumb_set_impath();\r\n}\r\nif (!defined(\'SMD_THUMB\')) {\r\n	define(\"SMD_THUMB\", \'smd_thumbnail\');\r\n}\r\nif (!defined(\'SMD_THUMB_ACTIVE\')) {\r\n	define(\"SMD_THUMB_ACTIVE\", 1);\r\n}\r\nif (!defined(\'SMD_THUMB_CROP\')) {\r\n	define(\"SMD_THUMB_CROP\", 2);\r\n}\r\nif (!defined(\'SMD_THUMB_SHARP\')) {\r\n	define(\"SMD_THUMB_SHARP\", 4);\r\n}\r\n\r\n// -------------------------------------------------------------\r\n// CSS definitions: hopefully kind to themers\r\nfunction smd_thumb_get_style_rules() {\r\n	$smd_thumb_styles = array(\r\n		\'smd_thumb\' =>\'\r\n.smd_selected { border:1px solid red; background:red; }\r\n#smd_thumbs img { padding:2px; margin:1px; border:1px solid black; }\r\n.thumbnail-alter { display:none; }\r\n.smd_hidden { display:none; }\r\n#smd_thumb_profiles { max-width:700px; margin:0 auto 18px; }\r\n#smd_thumb_profiles .txp-summary { text-align:left; }\r\n#smd_thumb_batch { float:right; }\r\n.smd_inactive { opacity:.3; }\r\n.smd_thumb_heading_active { cursor:pointer; }\r\n#smd_thumb_profile_form table { width:100% }\r\n.pref-label { text-align: right!important; }\r\n\'\r\n);\r\n\r\n	return $smd_thumb_styles;\r\n}\r\n\r\n// -------------------------------------------------------------\r\nfunction smd_thumb_inject_css($evt, $stp) {\r\n	global $event;\r\n\r\n	if ($event == \'image\') {\r\n		$smd_thumb_styles = smd_thumb_get_style_rules();\r\n		echo \'<style type=\"text/css\">\', $smd_thumb_styles[\'smd_thumb\'], \'</style>\';\r\n	}\r\n\r\n	return;\r\n}\r\n\r\n// -------------------------------------------------------------\r\nfunction smd_thumb_welcome($evt, $stp) {\r\n	switch ($stp) {\r\n		case \'installed\':\r\n			smd_thumb_table_install();\r\n			// Remove per-user prefs on upgrade from v0.1x to v0.20\r\n			safe_delete (\'txp_prefs\', \"name IN (\'smd_thumb_txp_create\', \'smd_thumb_txp_delete\', \'smd_thumb_auto_replace\') AND user_name != \'\'\");\r\n			break;\r\n		case \'deleted\':\r\n			smd_thumb_table_remove();\r\n			break;\r\n	}\r\n	return;\r\n}\r\n\r\n// ------------------------\r\n// Display the designated default thumbnail on the list page\r\nfunction smd_thumbs($evt, $stp, $dflt, $currimg) {\r\n	extract(gpsa(array(\'page\', \'sort\', \'dir\', \'crit\', \'search_method\')));\r\n\r\n	if (smd_thumb_table_exist()) {\r\n		$default = get_pref(\'smd_thumb_default_profile\', \'\', 1);\r\n		if ($default) {\r\n			$row = safe_row(\'*\', SMD_THUMB, \"name=\'\".sanitizeForUrl($default).\"\'\");\r\n			if ($row) {\r\n				$edit_url = \'?event=image\'.a.\'step=image_edit\'.a.\'id=\'.$currimg[\'id\'].a.\'sort=\'.$sort.\r\n						a.\'dir=\'.$dir.a.\'page=\'.$page.a.\'search_method=\'.$search_method.a.\'crit=\'.$crit;\r\n				$out = smd_thumb_img($row, $currimg);\r\n				return ($out) ? href($out, $edit_url) : gTxt(\'no\');\r\n			} else {\r\n				return gTxt(\'no\');\r\n			}\r\n		} else {\r\n			return gTxt(\'no\');\r\n		}\r\n	}\r\n}\r\n\r\n// ------------------------\r\n// Don\'t want the \'create\' controls or thumbnail image as they\'re now both handled in the edit portion of the screen\r\nfunction smd_thumb_empty($evt, $stp, $dflt, $currimg) {\r\n	return \' \';\r\n}\r\n\r\n// ------------------------\r\n// With a lot of images in the database, doing it all in one hit would time out.\r\n// So it\'s done via ajax; one request per image\r\nfunction smd_thumb_create_group($type, $lst=\'\') {\r\n	switch ($type) {\r\n		case \'all\':\r\n			$where = \'1=1\';\r\n			break;\r\n		case \'sel\':\r\n			$where = ($lst) ? \'id IN (\'.doSlash($lst).\')\' : \'1=0\';\r\n			break;\r\n		case \'cat\':\r\n			$where = \"category in (\'\".doSlash($lst).\"\')\";\r\n			break;\r\n		case \'usr\':\r\n			$where = \"author in (\'\".doSlash($lst).\"\')\";\r\n			break;\r\n	}\r\n\r\n	$images = safe_column(\'id\', \'txp_image\', $where);\r\n	$count = count($images);\r\n\r\n	echo \'<script type=\"text/javascript\">\';\r\n	echo \"var bctr = 0; var btot = {$count};\";\r\n	echo \'jQuery(function() {\';\r\n	echo \'jQuery(\"#smd_thumb_btot\").text(\"/\'.$count.\'\");\';\r\n	foreach ($images as $img) {\r\n		echo <<<EOJS\r\n		sendAsyncEvent(\r\n		{\r\n			event: textpattern.event,\r\n			step: \'smd_thumb_create_one\',\r\n			smd_thumb_imgid: {$img}\r\n		}, smd_create_group_feedback);\r\nEOJS;\r\n	}\r\n	echo \'});\';\r\n	echo <<<EOJS\r\n	function smd_create_group_feedback() {\r\n		bctr++;\r\n		jQuery(\'#smd_thumb_bcurr\').text(bctr);\r\n//		if (bctr >= btot) {\r\n//			jQuery(\'#smd_thumb_bcurr,#smd_thumb_btot\').empty();\r\n//		}\r\n	}\r\nEOJS;\r\n	echo \'</script>\';\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_create_one() {\r\n	$currimg = gps(\'smd_thumb_imgid\');\r\n	assert_int($currimg);\r\n\r\n	$rs = safe_rows(\'*\', SMD_THUMB, \'1=1 AND flags & \'.SMD_THUMB_ACTIVE);\r\n	$curr = safe_row(\'*\', \'txp_image\', \"id=\".doSlash($currimg));\r\n	if ($rs) {\r\n		smd_thumb_make($rs, $curr, 1);\r\n		send_xml_response();\r\n	}\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_switch_active() {\r\n	$name = doSlash(gps(\'smd_thumb_profile\'));\r\n	if ($name) {\r\n		safe_update(SMD_THUMB, \'flags = flags ^ \'.SMD_THUMB_ACTIVE, \"name=\'$name\'\");\r\n		send_xml_response(array(\'smd_thumb_profile\' => $name));\r\n	}\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_switch_pref() {\r\n	$name = doSlash(gps(\'smd_thumb_txptype\'));\r\n	$state = doSlash(gps(\'smd_thumb_state\'));\r\n	if ($name) {\r\n		set_pref(\'smd_thumb_\'.$name, $state, \'smd_thumb\', PREF_HIDDEN, \'text_input\');\r\n		send_xml_response();\r\n	}\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_generate($evt, $stp, $id=\'\') {\r\n	$id = ($id) ? $id : gps(\'id\');\r\n	$id = ($id) ? $id : $GLOBALS[\'ID\'];\r\n	$rs = safe_rows(\'*\', SMD_THUMB, \'1=1 AND flags & \'.SMD_THUMB_ACTIVE);\r\n\r\n	smd_thumb_make($rs, $id);\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_delete($evt, $stp) {\r\n	$ids = gps(\'selected\');\r\n	$rs = safe_rows(\'*\', SMD_THUMB, \'1=1\');\r\n\r\n	$images = safe_rows(\'*\', \'txp_image\', \'id IN (\' . join(\',\',quote_list($ids)) . \')\');\r\n	foreach ($images as $img) {\r\n		smd_thumb_unmake($rs, $img);\r\n	}\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_make($rs, $currimg, $force=0) {\r\n	// Wrapper for wet_thumb to allow multiple thumbnails\r\n	$msg = \'\';\r\n	smd_thumb_set_impath();\r\n\r\n	if (!class_exists(\'smd_thumb\')) {\r\n		class smd_thumb extends wet_thumb {\r\n			var $m_ext;\r\n			var $m_id;\r\n			var $m_dir;\r\n			var $m_dflt;\r\n			var $width;\r\n			var $height;\r\n			var $force;\r\n\r\n			// Constructor\r\n			public function __construct ($id, $dir, $img_row, $is_default=false, $pro_w=\'\', $pro_h=\'\', $force=0) {\r\n				$id = assert_int($id);\r\n				if ($img_row) {\r\n					extract($img_row);\r\n					$this->m_id = $id;\r\n					$this->m_ext = $ext;\r\n					$this->m_dir = $dir;\r\n					$this->force = $force;\r\n					$this->width = $pro_w;\r\n					$this->height = $pro_h;\r\n					$this->m_dflt = $is_default;\r\n				}\r\n				parent::__construct();\r\n			}\r\n\r\n			public function write_image() {\r\n				if ( !isset($this->m_ext) ) return false;\r\n\r\n				$autor = get_pref(\'smd_thumb_auto_replace\', \'0\');\r\n				$recfrom = get_pref(\'smd_thumb_create_from\', \'full\');\r\n				$src_sz = ($recfrom == \'full\') ? \'\' : \'t\';\r\n				$infile = IMPATH.$this->m_id.$src_sz.$this->m_ext;\r\n				$outfile = IMPATH.$this->m_dir.DS.$this->m_id.$this->m_ext;\r\n\r\n				if (!file_exists($outfile) || $autor || $this->force) {\r\n					// If this is the default profile and the pref indicates, write a Txp thumb too\r\n					if (($this->m_dflt == true) && (get_pref(\'smd_thumb_txp_create\', \'0\'))) {\r\n						$txp_thumb = IMPATH.$this->m_id.\'t\'.$this->m_ext;\r\n						if ( parent::write ( $infile, $txp_thumb ) ) {\r\n							safe_update(\'txp_image\', \"thumbnail = 1, thumb_w = $this->width, thumb_h = $this->height\", \'id = \'.$this->m_id);\r\n							@chmod($outfile, 0644);\r\n						}\r\n					}\r\n	\r\n					if ( parent::write ( $infile, $outfile ) ) {\r\n						@chmod($outfile, 0644);\r\n						return true;\r\n					}\r\n				}\r\n				return false;\r\n			}\r\n		}\r\n	}\r\n\r\n	// If passed only an ID, look up the rest of the image data\r\n	if (!is_array($currimg)) {\r\n		assert_int($currimg);\r\n		$currimg = safe_row(\'*\', \'txp_image\', \'id=\'.$currimg);\r\n	}\r\n\r\n	// Create each thumbnail\r\n	$pro_dflt = get_pref(\'smd_thumb_default_profile\', \'\');\r\n	foreach ($rs as $row) {\r\n		// Sanitize a little\r\n		$width = (int) $row[\'width\'];\r\n		$height = (int) $row[\'height\'];\r\n		if ($width == 0) $width = \'\';\r\n		if ($height == 0) $height = \'\';\r\n		if ($width === \'\' && $height === \'\') continue;\r\n		$crop = ($row[\'flags\'] & SMD_THUMB_CROP) ? 1 : 0;\r\n		$sharpen = ($row[\'flags\'] & SMD_THUMB_SHARP) ? 1 : 0;\r\n		$id = $currimg[\'id\'];\r\n		$is_dflt = ($row[\'name\'] == $pro_dflt) ? true : false;\r\n\r\n		$t = new smd_thumb($id, sanitizeForUrl($row[\'name\']), $currimg, $is_dflt, $width, $height, $force);\r\n		$t->extrapolate = true; // Allow bigger thumbs than original image\r\n		$t->crop = ($crop == \'1\');\r\n		$t->sharpen = ($sharpen == \'1\');\r\n		$t->hint = \'0\';\r\n		$t->width = $width;\r\n		$t->height = $height;\r\n		$t->quality = $row[\'quality\'];\r\n\r\n		if ($t->write_image()) {\r\n			$msg = gTxt(\'thumbnail_saved\', array(\'{id}\' => $id));\r\n		} else {\r\n			$msg = array(gTxt(\'thumbnail_not_saved\', array(\'{id}\' => $id)), E_ERROR);\r\n		}\r\n	}\r\n	return $msg;\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_unmake($rs, $currimg) {\r\n	$id = $currimg[\'id\'];\r\n	$ext = $currimg[\'ext\'];\r\n	$pro_dflt = get_pref(\'smd_thumb_default_profile\', \'\');\r\n	$txp_del = get_pref(\'smd_thumb_txp_delete\', \'0\');\r\n	smd_thumb_set_impath();\r\n\r\n	foreach ($rs as $row) {\r\n		$path = IMPATH.sanitizeForUrl($row[\'name\']).DS.$id.$ext;\r\n		if (file_exists($path)) {\r\n			unlink($path);\r\n		}\r\n\r\n		// Also remove Txp\'s built-in thumb?\r\n		if (($row[\'name\'] == $pro_dflt) && ($txp_del == \'1\')) {\r\n			$path = IMPATH.$id.\'t\'.$ext;\r\n			if (file_exists($path)) {\r\n				safe_update(\'txp_image\', \"thumbnail = 0, thumb_w = 0, thumb_h = 0\", \'id = \'.$id);\r\n				unlink($path);\r\n			}\r\n		}\r\n	}\r\n	return \'\';\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_insert() {\r\n	global $txpcfg, $txp_user, $page, $sort, $dir, $crit, $search_method;\r\n\r\n	smd_thumb_set_impath();\r\n	extract(gpsa(array(\'page\', \'sort\', \'dir\', \'crit\', \'search_method\')));\r\n\r\n	include_once txpath.\'/lib/txplib_misc.php\';\r\n\r\n	extract($txpcfg);\r\n	$id = assert_int(gps(\'id\'));\r\n	$profile = gps(\'smd_thumb_profile\');\r\n	$thumb_ext = gps(\'smd_thumb_ext\');\r\n\r\n	$author = fetch(\'author\', \'txp_image\', \'id\', $id);\r\n	if (!has_privs(\'image.edit\') && !($author == $txp_user && has_privs(\'image.edit.own\'))) {\r\n		return;\r\n	}\r\n\r\n	$file = $_FILES[\'thefile\'][\'tmp_name\'];\r\n	$name = $_FILES[\'thefile\'][\'name\'];\r\n\r\n	$file = get_uploaded_file($file);\r\n\r\n	if (empty($file))\r\n	{\r\n		return;\r\n	}\r\n\r\n	list($w, $h, $extension) = getimagesize($file);\r\n	$valid_exts = array(\r\n		IMAGETYPE_GIF  => \'.gif\',\r\n		IMAGETYPE_JPEG => \'.jpg\',\r\n		IMAGETYPE_PNG  => \'.png\',\r\n		IMAGETYPE_SWF  => \'.swf\',\r\n	);\r\n	$ext = isset($valid_exts[$extension]) ? $valid_exts[$extension] : \'\';\r\n\r\n	if (($file !== false) && $profile && $ext) {\r\n		$newpath = IMPATH.sanitizeForUrl($profile).DS.$id.$ext;\r\n		if (shift_uploaded_file($file, $newpath) == false) {\r\n		} else {\r\n			@chmod($newpath, 0644);\r\n\r\n			// If the pref indicates, duplicate as a Txp thumb too\r\n			if (get_pref(\'smd_thumb_txp_create\', \'0\') == 1) {\r\n				$txp_thumb = IMPATH.$id.\'t\'.$ext;\r\n				if ( copy ( $newpath, $txp_thumb ) ) {\r\n					safe_update(\'txp_image\', \"thumbnail = 1, thumb_w = $w, thumb_h = $h\", \'id = \'.$id);\r\n					@chmod($txp_thumb, 0644);\r\n				}\r\n			}\r\n			$message = gTxt(\'image_uploaded\', array(\'{name}\' => $name));\r\n		}\r\n	}\r\n\r\n	// Since the headers have been sent, resort to js to refresh the page\r\n	$url = \'?event=image\'.a.\'step=image_edit\'.a.\'id=\'.$id.a.\'sort=\'.$sort.a.\'dir=\'.$dir.a.\'page=\'.$page.a.\'search_method=\'.$search_method.a.\'crit=\'.$crit;\r\n	echo <<<EOS\r\n<script type=\"text/javascript\">\r\nwindow.location.href=\"{$url}\";\r\n</script>\r\n<noscript>\r\n<meta http-equiv=\"refresh\" content=\"0;url={$url}\" />\r\n</noscript>\r\nEOS;\r\n	exit;\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_edit($evt, $stp, $dflt, $currimg) {\r\n	global $step, $file_max_upload_size, $txp_user;\r\n\r\n	extract(gpsa(array(\r\n		\'id\',\r\n		\'page\',\r\n		\'sort\',\r\n		\'dir\',\r\n		\'crit\',\r\n		\'search_method\',\r\n		\'smd_thumbnail_size\',\r\n		\'smd_thumbnail_chosen_size\',\r\n		\'smd_thumbnail_delete\',\r\n		\'smd_step\'\r\n	)));\r\n\r\n	$id = ($id) ? $id : $GLOBALS[\'ID\'];\r\n\r\n	// Toggle profile panel\r\n	if ($step == \'save_pane_state\') {\r\n		smd_thumbnail_save_pane_state();\r\n		return;\r\n	}\r\n\r\n	// Create/delete the selected thumbs depending on the button pressed\r\n	if ($smd_step == \'smd_thumbnail_manage\') {\r\n		// Validate user\r\n		$author = fetch(\'author\', \'txp_image\', \'id\', $id);\r\n		if (!has_privs(\'image.edit\') && !($author == $txp_user && has_privs(\'image.edit.own\')))\r\n		{\r\n			image_list(gTxt(\'restricted_area\'));\r\n			return;\r\n		}\r\n\r\n		// Grab the thumbnails to work on\r\n		$where = ($smd_thumbnail_size==\'all\') ? \'1=1 AND flags & \'.SMD_THUMB_ACTIVE : \"name=\'\".doSlash($smd_thumbnail_chosen_size).\"\'\";\r\n		$rs = safe_rows(\'*\', SMD_THUMB, $where);\r\n\r\n		// Do it\r\n		if($smd_thumbnail_delete) {\r\n			$msg = smd_thumb_unmake($rs, $currimg);\r\n		} else {\r\n			$msg = smd_thumb_make($rs, $currimg, 1);\r\n		}\r\n	}\r\n\r\n	$ext = $currimg[\'ext\'];\r\n	echo script_js(<<<EOC\r\nfunction smd_thumb_selector(sel) {\r\n	var idx = 0;\r\n	jQuery(\"#smd_thumbs img\").each(function() {\r\n		if (jQuery(this).hasClass(sel)) {\r\n			jQuery(this).toggleClass(\'smd_selected\');\r\n			if (jQuery(this).hasClass(\'smd_selected\')) {\r\n				jQuery(\"#smd_upload_thumbnail\").attr(\'disabled\', false);\r\n				jQuery(\"#smd_thumb_profile\").val(sel);\r\n				idx = jQuery(\"#smd_thumbnail_size option[value=\'\"+sel+\"\']\").index();\r\n				jQuery(\"#smd_thumbnail_chosen_size\").val(sel);\r\n			} else {\r\n				jQuery(\"#smd_upload_thumbnail\").attr(\'disabled\', true);\r\n				jQuery(\"#smd_thumb_profile\").val(\'\');\r\n				jQuery(\"#smd_thumbnail_chosen_size\").val(\'\');\r\n			}\r\n		} else {\r\n			jQuery(this).removeClass(\'smd_selected\');\r\n		}\r\n	});\r\n	jQuery(\"#smd_thumbnail_size\").prop(\"selectedIndex\", idx);\r\n}\r\nfunction smd_thumb_select_changed() {\r\n	obj = jQuery(\"#smd_thumbnail_size\");\r\n	if (obj.attr(\"selectedIndex\") == 0) {\r\n		jQuery(\"#smd_upload_thumbnail\").attr(\'disabled\', true);\r\n		jQuery(\"#smd_thumb_profile\").val(\'\');\r\n		jQuery(\"#smd_thumbnail_chosen_size\").val(\'\');\r\n	} else {\r\n		jQuery(\"#smd_upload_thumbnail\").attr(\'disabled\', false);\r\n		jQuery(\"#smd_thumb_profile\").val(obj.val());\r\n		jQuery(\"#smd_thumbnail_chosen_size\").val(obj.val());\r\n	}\r\n	jQuery(\"#smd_thumbs img\").each(function() {\r\n		if (jQuery(this).hasClass(obj.val())) {\r\n			jQuery(this).addClass(\'smd_selected\');\r\n		} else {\r\n			jQuery(this).removeClass(\'smd_selected\');\r\n		}\r\n	});\r\n}\r\njQuery(function() {\r\n	jQuery(\"#smd_thumbs img\").each(function() {\r\n		var cls = jQuery(this).attr(\'class\');\r\n		jQuery(this).click(function() {\r\n			smd_thumb_selector(cls);\r\n		});\r\n	});\r\n	jQuery(\"#smd_upload_thumbnail\").attr(\'disabled\', true);\r\n	jQuery(\".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}\" />\');\r\n});\r\nEOC\r\n	);\r\n\r\n	// Add thumbnails and creation controls\r\n	if (smd_thumb_table_exist()) {\r\n		$rs = safe_rows(\'*\', SMD_THUMB, \'1=1 ORDER BY name\');\r\n		if ($rs) {\r\n			$profiles = array(\'all\' => gTxt(\'smd_thumb_all_sizes\'));\r\n			$thumbs[] = \'<div id=\"smd_thumbs\">\';\r\n			foreach ($rs as $row) {\r\n				if ($row[\'flags\'] & SMD_THUMB_ACTIVE) {\r\n					$profiles[$row[\'name\']] = $row[\'name\'];\r\n				}\r\n				$thumbs[] = smd_thumb_img($row, $currimg);\r\n			}\r\n			$thumbs[] = \'</div>\';\r\n			$qs = array(\r\n				\"event\" => \'image\',\r\n				\"step\" => \'image_edit\',\r\n				\"id\" => $id,\r\n				\"page\" => $page,\r\n				\"sort\" => $sort,\r\n				\"dir\" => $dir,\r\n				\"crit\" => $crit,\r\n				\"search_method\" => $search_method,\r\n			);\r\n\r\n			$out[] = \'<div id=\"smd_thumbnail_control\"><span class=\"txp-summary lever\'.(get_pref(\'pane_smd_thumbnails_visible\') ? \' expanded\' : \'\').\'\"><a href=\"#smd_thumbnails\">\'.gTxt(\'smd_thumb_thumbnail_heading\').\'</a></span><div id=\"smd_thumbnails\" class=\"toggle\" style=\"display:\'.(get_pref(\'pane_smd_thumbnails_visible\') ? \'block\' : \'none\').\'\">\';\r\n			$out[] = \'<form name=\"smd_thumbnail_create\" method=\"post\" action=\"\'.join_qs($qs).\'\">\';\r\n			$out[] = fInput(\'hidden\', \'smd_step\', \'smd_thumbnail_manage\');\r\n			$out[] = fInput(\'hidden\', \'smd_thumbnail_chosen_size\', \'\', \'\', \'\', \'\', \'\', \'\', \'smd_thumbnail_chosen_size\');\r\n			$out[] = selectInput(\'smd_thumbnail_size\', $profiles, \'\', \'\', \' onchange=\"return smd_thumb_select_changed()\";\', \'smd_thumbnail_size\');\r\n			$out[] = fInput(\'submit\', \'\', gTxt(\'create\'));\r\n			$out[] = fInput(\'submit\', \'smd_thumbnail_delete\', gTxt(\'delete\'));\r\n			$out[] = join(\'\', $thumbs);\r\n			$out[] = \'</form>\';\r\n			$out[] = upload_form(gTxt(\'smd_thumb_upload\'), \'upload_thumbnail\', \'smd_thumbnail_insert\', \'image\', $id, $file_max_upload_size, \'smd_upload_thumbnail\', \'thumbnail-upload\');\r\n			$out[] = \'</div>\';\r\n			return join(\'\', $out);\r\n		}\r\n	}\r\n	return \' \';\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_img($row, $currimg, $meta=array(), $dsp=\'\') {\r\n	global $img_dir, $smd_thumb_data;\r\n\r\n	smd_thumb_set_impath();\r\n\r\n	$dir = sanitizeForUrl($row[\'name\']);\r\n	$id = $currimg[\'id\'];\r\n	$ext = $currimg[\'ext\'];\r\n	// alt is a mandatory attribute so make sure it exists (even if it =\"\")\r\n	if (!isset($meta[\'alt\'])) {\r\n		$meta[\'alt\'] = $currimg[\'alt\'];\r\n	}\r\n\r\n	$path = IMPATH.$dir.DS.$id.$ext;\r\n\r\n	if (file_exists($path)) {\r\n		$extras = \'\';\r\n		if (isset($meta[\'forcew\']) || isset($meta[\'forceh\'])) {\r\n			$dims = getimagesize($path);\r\n			if (isset($meta[\'forcew\']) && !$row[\'width\']) {\r\n				$row[\'width\'] = $dims[0];\r\n			}\r\n			if (isset($meta[\'forceh\']) && !$row[\'height\']) {\r\n				$row[\'height\'] = $dims[1];\r\n			}\r\n			unset($meta[\'forcew\']);\r\n			unset($meta[\'forceh\']);\r\n		}\r\n\r\n		if (isset($meta[\'width\']) && $meta[\'width\'] !== \'\') {\r\n			$row[\'width\'] = $meta[\'width\'];\r\n		}\r\n		if (isset($meta[\'height\']) && $meta[\'height\'] !== \'\') {\r\n			$row[\'height\'] = $meta[\'height\'];\r\n		}\r\n		unset($meta[\'width\']);\r\n		unset($meta[\'height\']);\r\n\r\n		// \'empty\' includes zero dimensions which omits the attribute and allows the browser to scale\r\n		$w = (!empty($row[\'width\'])) ? \' width=\"\'.$row[\'width\'].\'\"\': \'\';\r\n		$h = (!empty($row[\'height\'])) ? \' height=\"\'.$row[\'height\'].\'\"\': \'\';\r\n\r\n		$uDate = \'\';\r\n		if (!isset($meta[\'stamp\'])) {\r\n			$uDate = \'?\'.filemtime($path);\r\n		}\r\n		unset($meta[\'stamp\']);\r\n\r\n		if (!isset($meta[\'class\'])) {\r\n			$meta[\'class\'] = $dir;\r\n		}\r\n\r\n		foreach ($meta as $key => $val) {\r\n			// We need all atts for container tags, but only valid HTML atts should appear in the default <img> tag\r\n			if (in_array($key, array(\'alt\', \'class\', \'title\'))) {\r\n				$extras .= \' \'.$key.\'=\"\'.$val.\'\"\';\r\n			}\r\n		}\r\n\r\n		if ($dsp) {\r\n			// Hand off to the form/container for formatting advice\r\n			$smd_thumb_data = $meta;\r\n			$smd_thumb_data[\'id\'] = $id;\r\n			$smd_thumb_data[\'ext\'] = $ext;\r\n			$smd_thumb_data[\'alt\'] = $alt;\r\n			$smd_thumb_data[\'w\'] = $row[\'width\'];\r\n			$smd_thumb_data[\'h\'] = $row[\'height\'];\r\n			$smd_thumb_data[\'html_w\'] = $w;\r\n			$smd_thumb_data[\'html_h\'] = $h;\r\n			return parse($dsp);\r\n		} else {\r\n			return \'<img src=\"\'.ihu.$img_dir.\'/\'.$dir.\'/\'.$id.$ext.$uDate.\'\"\'.$w.$h.$extras.\' />\';\r\n		}\r\n	}\r\n\r\n	return \'\';\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_profiles($evt, $stp, $dflt, $imglist) {\r\n	global $smd_thumb_event, $step, $smd_thumb_prevs, $txp_user;\r\n\r\n	if (!has_privs(__FUNCTION__)) {\r\n		return;\r\n	}\r\n\r\n	extract(gpsa(array(\r\n		\'page\',\r\n		\'sort\',\r\n		\'dir\',\r\n		\'crit\',\r\n		\'search_method\',\r\n		\'smd_thumb_add\',\r\n		\'smd_thumb_cancel\',\r\n		\'smd_thumb_save\',\r\n		\'smd_thumb_name\',\r\n		\'smd_thumb_newname\',\r\n		\'smd_thumb_width\',\r\n		\'smd_thumb_height\',\r\n		\'smd_thumb_quality\',\r\n		\'smd_thumb_active\',\r\n		\'smd_thumb_active_new\',\r\n		\'smd_thumb_crop\',\r\n		\'smd_thumb_sharpen\',\r\n		\'smd_thumb_default\',\r\n		\'smd_thumb_selected\',\r\n		\'smd_thumb_cat_selected\',\r\n		\'smd_thumb_usr_selected\',\r\n		\'smd_thumb_group_type\',\r\n	)));\r\n\r\n	// Sanitize\r\n	$quality = (is_numeric($smd_thumb_quality)) ? (($smd_thumb_quality < 0) ? 75 : (($smd_thumb_quality > 100) ? 75 : $smd_thumb_quality) ) : 75;\r\n	$width = (is_numeric($smd_thumb_width)) ? $smd_thumb_width : 0;\r\n	$height = (is_numeric($smd_thumb_height)) ? $smd_thumb_height : 0;\r\n	$newname = sanitizeForUrl($smd_thumb_newname);\r\n	$msg = \'\';\r\n\r\n	smd_thumb_set_impath();\r\n\r\n	// Table control\r\n	if (gps(\'remove\')) {\r\n		smd_thumb_table_remove();\r\n	} else {\r\n		if (!smd_thumb_table_exist()) {\r\n			smd_thumb_table_install();\r\n		}\r\n	}\r\n\r\n	// Toggle profile panel\r\n	if ($step == \'save_pane_state\') {\r\n		smd_thumbnail_save_pane_state();\r\n		return;\r\n	}\r\n\r\n	if ($step == \'smd_thumb_create_group\') {\r\n		switch ($smd_thumb_group_type) {\r\n			case \'all\':\r\n				smd_thumb_create_group(\'all\');\r\n				break;\r\n			case \'sel\':\r\n				smd_thumb_create_group(\'sel\', rtrim($smd_thumb_selected, \',\'));\r\n				break;\r\n			case \'cat\':\r\n				smd_thumb_create_group(\'cat\', $smd_thumb_cat_selected);\r\n				break;\r\n			case \'usr\':\r\n				smd_thumb_create_group(\'usr\', $smd_thumb_usr_selected);\r\n				break;\r\n		}\r\n		set_pref(\'pane_smd_thumb_group_type\', $smd_thumb_group_type, \'smd_thumb\', PREF_HIDDEN, \'text_input\', 0, PREF_PRIVATE);\r\n		$step = \'smd_thumb_prefs\';\r\n	}\r\n\r\n	// Save profile\r\n	if ($step == \'smd_thumb_profile_save\') {\r\n		if (smd_thumb_table_exist()) {\r\n			if ($smd_thumb_cancel) {\r\n				// Do nothing\r\n			} else if ($smd_thumb_add) {\r\n				// Create new profile\r\n				$exists = safe_row(\'*\', SMD_THUMB, \"name=\'\".doSlash($newname).\"\'\");\r\n				if ($exists) {\r\n					$msg = gTxt(\'smd_thumb_profile_exists\', array(\'{name}\' => doSlash($newname)));\r\n				} else {\r\n					if ($newname) {\r\n						$flags = 0;\r\n						$flags = ($smd_thumb_active_new) ? $flags | SMD_THUMB_ACTIVE : $flags;\r\n						$flags = ($smd_thumb_crop) ? $flags | SMD_THUMB_CROP : $flags;\r\n						$flags = ($smd_thumb_sharpen) ? $flags | SMD_THUMB_SHARP : $flags;\r\n\r\n						safe_insert(SMD_THUMB,\r\n							\"name=\'\".doSlash($newname).\"\'\".\r\n							\", width=\'\".doSlash($width).\"\'\".\r\n							\", height=\'\".doSlash($height).\"\'\".\r\n							\", quality=\'\".doSlash($quality).\"\'\".\r\n							\", flags=\'\".doSlash($flags).\"\'\"\r\n						);\r\n\r\n						// Alter the default thumbnail\r\n						if ($smd_thumb_default) {\r\n							set_pref(\'smd_thumb_default_profile\', $newname, \'smd_thumb\', PREF_HIDDEN);\r\n						}\r\n\r\n						// Create the thumbnail directory\r\n						@mkdir(IMPATH.$newname);\r\n					}\r\n				}\r\n			} else {\r\n				// Update existing profile\r\n				$name = sanitizeForUrl($smd_thumb_name);\r\n				$exists = safe_row(\'*\', SMD_THUMB, \"name=\'\".doSlash($newname).\"\'\");\r\n				if ($exists && strtolower($newname) != strtolower($name)) {\r\n					$msg = gTxt(\'smd_thumb_profile_exists\', array(\'{name}\' => doSlash($newname)));\r\n				} else {\r\n					if ($newname) {\r\n						$flags = 0;\r\n						$flags = ($smd_thumb_active) ? $flags | SMD_THUMB_ACTIVE : $flags;\r\n						$flags = ($smd_thumb_crop) ? $flags | SMD_THUMB_CROP : $flags;\r\n						$flags = ($smd_thumb_sharpen) ? $flags | SMD_THUMB_SHARP : $flags;\r\n\r\n						safe_update(\r\n							SMD_THUMB,\r\n							\"name=\'\".doSlash($newname).\"\'\"\r\n								.\", width=\'\".doSlash($width).\"\'\"\r\n								.\", height=\'\".doSlash($height).\"\'\"\r\n								.\", quality=\'\".doSlash($quality).\"\'\"\r\n								.\", flags=\'\".doSlash($flags).\"\'\"\r\n							, \"name=\'\".doSlash($name).\"\'\"\r\n						);\r\n\r\n						// Alter the default thumbnail\r\n						if ($smd_thumb_default) {\r\n							set_pref(\'smd_thumb_default_profile\', $newname, \'smd_thumb\', PREF_HIDDEN);\r\n						} else {\r\n							// Remove the default flag if this used to be the default\r\n							if (get_pref(\'smd_thumb_default_profile\') == doSlash($name)) {\r\n								set_pref(\'smd_thumb_default_profile\', \'\', \'smd_thumb\', PREF_HIDDEN);\r\n							}\r\n						}\r\n\r\n						// Adjust the thumbnail directory if the name has changed\r\n						if ($newname != $name) {\r\n							rename(IMPATH.$name, IMPATH.$newname);\r\n						}\r\n					}\r\n				}\r\n			}\r\n		} else {\r\n			$msg = gTxt(\'smd_thumb_tables_not_installed\');\r\n		}\r\n		$step = \'smd_thumb_profile\';\r\n	}\r\n\r\n	// Delete profile\r\n	if ($step == \'smd_thumb_profile_delete\') {\r\n		if (smd_thumb_table_exist()) {\r\n			$txp_del = get_pref(\'smd_thumb_txp_delete\');\r\n			$the_dflt = get_pref(\'smd_thumb_default_profile\', \'\', 1);\r\n\r\n			if (!empty($smd_thumb_name)) {\r\n				$name = sanitizeForUrl($smd_thumb_name);\r\n				$ret = safe_delete(SMD_THUMB, \"name=\'\".doSlash($name).\"\'\");\r\n				if ($ret) {\r\n					smd_thumb_rmdir(IMPATH.$name);\r\n\r\n					// Delete all Txp thumbs too?\r\n					if (($name == $the_dflt) && ($txp_del == \'1\')) {\r\n						$rs = safe_rows(\'id, ext\', \'txp_image\', \'thumbnail = 1\');\r\n						foreach ($rs as $row) {\r\n							$path = IMPATH.$row[\'id\'].\'t\'.$row[\'ext\'];\r\n							if (file_exists($path)) {\r\n								unlink($path);\r\n							}\r\n						}\r\n						safe_update(\'txp_image\', \'thumbnail = 0, thumb_w = 0, thumb_h = 0\', \'thumbnail = 1\');\r\n					}\r\n\r\n					// Remove the default flag if this used to be the default\r\n					if (get_pref(\'smd_thumb_default_profile\') == doSlash($name)) {\r\n						set_pref(\'smd_thumb_default_profile\', \'\', \'smd_thumb\', PREF_HIDDEN);\r\n					}\r\n\r\n					$msg = gTxt(\'smd_thumb_profile_deleted\', array(\'{name}\' => doSlash($name)));\r\n				}\r\n			}\r\n		}\r\n		$step = \'smd_thumb_profile\';\r\n	}\r\n\r\n	$qs = array(\r\n		\"event\" => \'image\',\r\n		\"page\" => $page,\r\n		\"sort\" => $sort,\r\n		\"dir\" => $dir,\r\n		\"crit\" => $crit,\r\n		\"search_method\" => $search_method,\r\n	);\r\n\r\n	$privs = safe_field(\'privs\', \'txp_users\', \"name = \'\".doSlash($txp_user).\"\'\");\r\n	$rights = in_array($privs, $smd_thumb_prevs) ? true : false;\r\n\r\n	if ($step == \'smd_thumb_prefs\') {\r\n\r\n		// ** THUMBNAIL PREFS **\r\n\r\n		if (smd_thumb_table_exist()) {\r\n			$pro_dflt = get_pref(\'smd_thumb_default_profile\', \'\', 1);\r\n\r\n			// Subselect lists for type \'cat\' and \'usr\'\r\n			$rs = getTree(\'root\', \'image\');\r\n			$allCats = $rs ? treeSelectInput(\'smd_thumb_cat_selected\', $rs, \'\') : \'\';\r\n			$allCats = \"<span>\".str_replace(array(\"\\n\", \'-\'), array(\'\', \'&#45;\'), str_replace(\'</\', \'<\\/\', addslashes($allCats))).\"<\\/span>\";\r\n			$rs = safe_column(\'name\', \'txp_users\', \"privs not in(0,6) order by name asc\");\r\n			$allUsrs = $rs ? selectInput(\'smd_thumb_usr_selected\', $rs, \'\', true) : \'\';\r\n			$allUsrs = \"<span>\".str_replace(array(\"\\n\", \'-\'), array(\'\', \'&#45;\'), str_replace(\'</\', \'<\\/\', addslashes($allUsrs))).\"<\\/span>\";\r\n\r\n			echo script_js(<<<EOC\r\nfunction smd_thumb_switch_pref(obj, name) {\r\n	if (name == \'create_from\') {\r\n		state = jQuery(obj).val();\r\n	} else {\r\n		state = (jQuery(obj).prop(\'checked\')) ? 1 : 0;\r\n	}\r\n	sendAsyncEvent(\r\n	{\r\n		event: textpattern.event,\r\n		step: \'smd_thumb_switch_pref\',\r\n		smd_thumb_txptype: name,\r\n		smd_thumb_state: state\r\n	});\r\n}\r\nfunction smd_thumb_copy_selected() {\r\n	jQuery(\"#images_form tbody .multi-edit input:checked, #images_form tbody .txp-list-col-multi-edit input:checked\").each(function() {\r\n		jQuery(\"#smd_thumb_selected\").val(jQuery(\"#smd_thumb_selected\").val() + jQuery(this).val() + \',\');\r\n	});\r\n}\r\nfunction smd_thumb_subsel(obj) {\r\n	obj = jQuery(obj);\r\n	dest = jQuery(\'#smd_thumb_subsel\');\r\n	item = obj.val();\r\n	switch (item) {\r\n		case \'all\':\r\n		case \'sel\':\r\n			dest.empty();\r\n			break;\r\n		case \'cat\':\r\n			dest.empty().prepend(\'{$allCats}\');\r\n			break;\r\n		case \'usr\':\r\n			dest.empty().prepend(\'{$allUsrs}\');\r\n			break;\r\n	}\r\n}\r\njQuery(function() {\r\n	jQuery(\'#smd_thumb_group_type\').change();\r\n});\r\nEOC\r\n		);\r\n\r\n			$pnlbtn = \'<a class=\"navlink\" href=\"?event=image\'.a.\'sort=\'.$sort.a.\'dir=\'.$dir.a.\'page=\'.$page.a.\'search_method=\'.$search_method.a.\'crit=\'.$crit.\'\">\'.gTxt(\'smd_thumb_btn_pnl\').\'</a>\';\r\n			$grpbtn = \'<button class=\"navlink\" type=\"submit\" onclick=\"smd_thumb_copy_selected(); return confirm(\\\'\'.gTxt(\'smd_thumb_create_group_confirm\').\'\\\');\">\'.gTxt(\'smd_thumb_all_thumbs\').\'</button>\';\r\n			$grpTypes = array(\r\n				\'sel\' => gTxt(\'smd_thumb_bysel\'),\r\n				\'cat\' => gTxt(\'smd_thumb_bycat\'),\r\n				\'usr\' => gTxt(\'smd_thumb_byusr\'),\r\n				\'all\' => gTxt(\'smd_thumb_byall\'),\r\n			);\r\n\r\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>\';\r\n\r\n			$out[] = \'<div id=\"smd_thumb_profiles\"><div 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></div><div id=\"smd_thumbnail_profiles\" class=\"toggle\" style=\"display:\'.(get_pref(\'pane_smd_thumbnail_profiles_visible\') ? \'block\' : \'none\').\'\">\';\r\n			$out[] = \'<div class=\"txp-buttons\">\'.$pnlbtn.\'</div>\';\r\n\r\n			$out[] = \'<div><div>\'.gTxt(\'smd_thumb_batch_legend\').\'</div>\';\r\n			$out[] = \'<div id=\"smd_thumb_batch\"><span id=\"smd_thumb_bcurr\"></span><span id=\"smd_thumb_btot\"></span></div>\';\r\n			$out[] = \'<form method=\"post\" name=\"smd_thumb_multi_edit\" id=\"smd_thumb_multi_edit\" action=\"\'.join_qs($qs).\'\">\';\r\n			$out[] = startTable();\r\n			$out[] = tr(td(gTxt(\'smd_thumb_batch_preamble\').$grpbtn.$grpOpts));\r\n			$out[] = endTable();\r\n			$out[] = fInput(\'hidden\', \'smd_thumb_selected\', \'\', \'\', \'\', \'\', \'\', \'\', \'smd_thumb_selected\');\r\n			$out[] = eInput($smd_thumb_event);\r\n			$out[] = sInput(\'smd_thumb_create_group\');\r\n			$out[] = \'</form>\';\r\n			$out[] = \'</div>\';\r\n\r\n			if ($rights) {\r\n				$out[] = \'<div><div>\'.gTxt(\'smd_thumb_prefs\').\'</div>\';\r\n				$out[] = \'<form method=\"post\" name=\"smd_thumb_prefs\" id=\"smd_thumb_prefs_form\" action=\"\'.join_qs($qs).\'\">\';\r\n				$out[] = startTable();\r\n\r\n				$txp_rf = get_pref(\'smd_thumb_create_from\', \'full\');\r\n				$out[] = tr(\r\n					tda(gTxt(\'smd_thumb_txp_create_from\'), \' class=\"pref-label\"\') .\r\n					tda(\r\n						\'<input type=\"radio\" name=\"smd_thumb_create_from\" value=\"full\"\'.(($txp_rf==\'full\') ? \' checked=\"checked\"\' : \'\'). \' class=\"radio\" onchange=\"smd_thumb_switch_pref(this, \\\'create_from\\\');\" />\'.gTxt(\'smd_thumb_txp_create_from_full\')\r\n						.\'<input type=\"radio\" name=\"smd_thumb_create_from\" value=\"thumb\"\'.(($txp_rf==\'thumb\') ? \' checked=\"checked\"\' : \'\'). \' class=\"radio\" onchange=\"smd_thumb_switch_pref(this, \\\'create_from\\\');\" />\'.gTxt(\'smd_thumb_txp_create_from_thumb\')\r\n					));\r\n\r\n				if ($pro_dflt) {\r\n					$txp_c = get_pref(\'smd_thumb_txp_create\');\r\n					$txp_d = get_pref(\'smd_thumb_txp_delete\');\r\n					$out[] = tr(\r\n						tda(gTxt(\'smd_thumb_txp_default_sync\'), \' class=\"pref-label\"\') .\r\n						tda(\r\n							\'<input type=\"checkbox\" name=\"smd_thumb_txp_create\" value=\"1\"\'.(($txp_c) ? \' checked=\"checked\"\' : \'\'). \' class=\"checkbox\" onchange=\"smd_thumb_switch_pref(this, \\\'txp_create\\\');\" />\'.\r\n							gTxt(\'smd_thumb_create\').\r\n							\'<input type=\"checkbox\" name=\"smd_thumb_txp_delete\" value=\"1\"\'.(($txp_d) ? \' checked=\"checked\"\' : \'\'). \' class=\"checkbox\" onchange=\"smd_thumb_switch_pref(this, \\\'txp_delete\\\');\" />\'.\r\n							gTxt(\'smd_thumb_delete\')\r\n						));\r\n				}\r\n\r\n				$txp_ar = get_pref(\'smd_thumb_auto_replace\');\r\n				$out[] = tr(\r\n					tda(gTxt(\'smd_thumb_txp_auto_replace\'), \' class=\"pref-label noline\"\') .\r\n					tda(\r\n						\'<input type=\"checkbox\" name=\"smd_thumb_auto_replace\" value=\"1\"\'.(($txp_ar) ? \' checked=\"checked\"\' : \'\'). \' class=\"checkbox\" onchange=\"smd_thumb_switch_pref(this, \\\'auto_replace\\\');\" />\'\r\n					, \' class=\"noline\"\'));\r\n\r\n				$out[] = endTable();\r\n				$out[] = \'</form></div>\';\r\n			}\r\n			$out[] = \'</div></div>\';\r\n		}\r\n	} else {\r\n\r\n		// ** THUMBNAIL PROFILES **\r\n\r\n		$editFocus = ($step == \'smd_thumb_profile_edit\') ? \'jQuery(\"#smd_thumb_profile_edited input[name=\\\'smd_thumb_newname\\\']\").focus();\' : \'\';\r\n\r\n		echo script_js(<<<EOC\r\nfunction smd_thumb_togglenew() {\r\n	box = jQuery(\"#smd_thumb_profile_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\nfunction smd_thumb_switch_active(name) {\r\n	sendAsyncEvent(\r\n	{\r\n		event: textpattern.event,\r\n		step: \'smd_thumb_switch_active\',\r\n		smd_thumb_profile: name\r\n	}, smd_thumb_switch_active_done);\r\n}\r\nfunction smd_thumb_switch_active_done(data) {\r\n	var smd_thumb_profile = jQuery(\'#smd_thumb_profile_\'+jQuery(data).find(\'smd_thumb_profile\').attr(\'value\'));\r\n	smd_thumb_profile.toggleClass(\'smd_inactive\');\r\n	if (smd_thumb_profile.hasClass(\'smd_inactive\')) {\r\n		smd_thumb_profile.find(\'input:checkbox\').removeAttr(\'checked\');\r\n	} else {\r\n		smd_thumb_profile.find(\'input:checkbox\').attr(\'checked\', \'checked\');\r\n	}\r\n}\r\n\r\njQuery(function() {\r\n	jQuery(\'.smd_thumb_heading_active\').click(function() {\r\n		jQuery(\'.smd_thumb_row input:checkbox\').each(function() {\r\n			smd_thumb_switch_active(jQuery(this).parent().attr(\'class\'));\r\n		});\r\n	});\r\n	{$editFocus}\r\n});\r\nEOC\r\n		);\r\n\r\n		$newbtn = \'<a class=\"navlink\" href=\"#\" onclick=\"return smd_thumb_togglenew();\">\'.gTxt(\'smd_thumb_new\').\'</a>\';\r\n		$prefbtn = \'<a class=\"navlink\" 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.\'\">\'.(($rights) ? gTxt(\'smd_thumb_btn_tools_prefs\') : gTxt(\'smd_thumb_btn_tools\')).\'</a>\';\r\n\r\n		$headings = n.\'<thead>\'.tr(\r\n			n.column_head(\'name\', \'name\', \'image\', false, \'asc\').\r\n			column_head(\'thumb_width\', \'width\', \'image\', false).\r\n			column_head(\'thumb_height\', \'height\', \'image\', false).\r\n			column_head(gTxt(\'smd_thumb_quality\'), \'quality\', \'image\', false).\r\n			column_head(\'keep_square_pixels\', \'crop\', \'image\', false).\r\n			column_head(gTxt(\'smd_thumb_sharpen\'), \'sharpen\', \'image\', false).\r\n			column_head(\'active\', \'active\', \'image\', false, \'\', \'\', \'\', \'smd_thumb_heading_active\').\r\n			column_head(\'default\', \'default\', \'image\', false).\r\n			column_head(gTxt(\'smd_thumb_actions\'), \'actions\', \'image\', false)\r\n		).\'</thead>\';\r\n\r\n		$out[] = \'<div id=\"smd_thumb_profiles\"><div class=\"txp-summary lever\'.(get_pref(\'pane_smd_thumbnail_profiles_visible\') ? \' expanded\' : \'\').\'\"><a href=\"#smd_thumbnail_profiles\">\'.gTxt(\'smd_thumb_profile_heading\').\'</a></div><div id=\"smd_thumbnail_profiles\" class=\"toggle\" style=\"display:\'.(get_pref(\'pane_smd_thumbnail_profiles_visible\') ? \'block\' : \'none\').\'\">\';\r\n		$out[] = \'<div class=\"txp-buttons\">\'.$newbtn.n.$prefbtn.\'</div>\';\r\n\r\n		// Main list of profiles\r\n		$out[] = \'<form method=\"post\" name=\"smd_thumb_profile_form\" id=\"smd_thumb_profile_form\" action=\"\'.join_qs($qs).\'\">\';\r\n		$out[] = startTable();\r\n		$out[] = $headings;\r\n\r\n		if (smd_thumb_table_exist()) {\r\n			$rs = safe_rows(\'*\', SMD_THUMB, \'1=1 ORDER BY name\');\r\n			$pro_dflt = get_pref(\'smd_thumb_default_profile\', \'\', 1);\r\n\r\n			// New Profile row\r\n			$out[]= \'<tr id=\"smd_thumb_profile_create\" class=\"smd_hidden\">\';\r\n			$out[] = td(sInput(\'smd_thumb_profile_save\').fInput(\'text\', \'smd_thumb_newname\', (($step==\'smd_thumb_profile_save\') ? $smd_thumb_name : \'\'), \'smd_focus\'))\r\n				.td(fInput(\'text\', \'smd_thumb_width\', (($step==\'smd_thumb_profile_save\') ? $width : \'\'), \'\', \'\', \'\', \'4\'))\r\n				.td(fInput(\'text\', \'smd_thumb_height\', (($step==\'smd_thumb_profile_save\') ? $height : \'\'), \'\', \'\', \'\', \'4\'))\r\n				.td(fInput(\'text\', \'smd_thumb_quality\', (($step==\'smd_thumb_profile_save\') ? $quality : \'\'), \'\', \'\', \'\', \'4\'))\r\n				.td(checkbox(\'smd_thumb_crop\', \'1\', (($step==\'smd_thumb_profile_save\') ? $smd_thumb_crop : 0)))\r\n				.td(checkbox(\'smd_thumb_sharpen\', \'1\', (($step==\'smd_thumb_profile_save\') ? $smd_thumb_sharpen : 0)))\r\n				.td(checkbox(\'smd_thumb_active_new\', \'1\', 1))\r\n				.td(checkbox(\'smd_thumb_default\', \'1\', 0))\r\n				.td(fInput(\'submit\', \'smd_thumb_add\', gTxt(\'add\')));\r\n			$out[]= \'</tr>\';\r\n\r\n			// Remaining rows\r\n			foreach ($rs as $row) {\r\n				$link_edt = join_qs($qs).a.\'step=smd_thumb_profile_edit\'.a.\'smd_thumb_name=\'.$row[\'name\'];\r\n				$link_del = join_qs($qs).a.\'step=smd_thumb_profile_delete\'.a.\'smd_thumb_name=\'.$row[\'name\'];\r\n				$btnEdt = \'<a href=\"\'.$link_edt.\'\">[\' . gTxt(\'edit\') . \']</a>\';\r\n				$btnDel = \'<a href=\"\'.$link_del.\'\" onclick=\"return confirm(\\\'\'.gTxt(\'smd_thumb_delete_confirm\', array(\"{name}\" => $row[\'name\'])).\'\\\');\">[\' . gTxt(\'delete\') . \']</a>\';\r\n				$active = ($row[\'flags\'] & SMD_THUMB_ACTIVE) ? 1 : 0;\r\n				$crop = ($row[\'flags\'] & SMD_THUMB_CROP) ? 1 : 0;\r\n				$sharpen = ($row[\'flags\'] & SMD_THUMB_SHARP) ? 1 : 0;\r\n\r\n				if ($step == \'smd_thumb_profile_edit\' && $row[\'name\'] == $smd_thumb_name) {\r\n					$btnSave = fInput(\'submit\', \'smd_thumb_save\', gTxt(\'Save\'));\r\n					$btnCancel = fInput(\'submit\', \'smd_thumb_cancel\', gTxt(\'Cancel\'));\r\n					$out[] = tr(\r\n						td(hInput(\'smd_thumb_name\', $row[\'name\']).fInput(\'text\', \'smd_thumb_newname\', $row[\'name\']))\r\n						.td(fInput(\'text\', \'smd_thumb_width\', $row[\'width\'], \'\', \'\', \'\', \'4\'))\r\n						.td(fInput(\'text\', \'smd_thumb_height\', $row[\'height\'], \'\', \'\', \'\', \'4\'))\r\n						.td(fInput(\'text\', \'smd_thumb_quality\', $row[\'quality\'], \'\', \'\', \'\', \'4\'))\r\n						.td(checkbox(\'smd_thumb_crop\', \'1\', $crop))\r\n						.td(checkbox(\'smd_thumb_sharpen\', \'1\', $sharpen))\r\n						.td(checkbox(\'smd_thumb_active\', \'1\', $active), \'\', $row[\'name\'])\r\n						.td(checkbox(\'smd_thumb_default\', \'1\', (($row[\'name\'] == $pro_dflt) ? 1 : 0)))\r\n						.td($btnSave.$btnCancel)\r\n					, \' id=\"smd_thumb_profile_edited\"\');\r\n				} else {\r\n					$out[] = tr(\r\n						td(href($row[\'name\'], $link_edt)).\r\n						td($row[\'width\']).\r\n						td($row[\'height\']).\r\n						td($row[\'quality\']).\r\n						td(($crop) ? gTxt(\'yes\') : gTxt(\'no\')).\r\n						td(($sharpen) ? gTxt(\'yes\') : gTxt(\'no\')).\r\n						td(\'<input type=\"checkbox\" name=\"smd_thumb_active_\'.$row[\'name\'].\'\" value=\"1\"\'.(($active) ? \' checked=\"checked\"\' : \'\'). \' class=\"checkbox\" onchange=\"smd_thumb_switch_active(\\\'\'.$row[\'name\'].\'\\\');\" />\', \'\', $row[\'name\']).\r\n						td(($row[\'name\'] == $pro_dflt) ? gTxt(\'yes\') : gTxt(\'no\')).\r\n						td($btnDel)\r\n					, \' id=\"smd_thumb_profile_\'.$row[\'name\'].\'\" class=\"smd_thumb_row\' . (($active) ? \'\' : \' smd_inactive\').\'\"\');\r\n				}\r\n			}\r\n		}\r\n\r\n		$out[] = endTable();\r\n		$out[] = \'</form></div></div>\';\r\n	}\r\n\r\n	return join(\'\', $out);\r\n}\r\n\r\n// ------------------------\r\n// Useful function for other plugins to use to retrieve a list of current profiles\r\nfunction smd_thumb_get_profiles($active=1, $order=\'name\') {\r\n	$allowedOrders = array(\'name\', \'width\', \'height\',\'quality\');\r\n	$order = in_array($order, $allowedOrders) ? $order : \'name\';\r\n\r\n	$where[] = \'1=1\';\r\n	if ($active) {\r\n		$where[] = \'flags & \'.SMD_THUMB_ACTIVE;\r\n	}\r\n\r\n	$rs = safe_rows(\'*\', SMD_THUMB, join(\' AND \', $where) . \' ORDER BY \'.$order);\r\n\r\n	return $rs;\r\n}\r\n\r\n// ------------------------\r\n// Delete a thumbnail directory and its contents\r\nfunction smd_thumb_rmdir($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 (filetype($dir.DS.$object) == \"dir\") smd_thumb_rmdir($dir.DS.$object); else unlink($dir.DS.$object);\r\n			}\r\n		}\r\n		reset($objects);\r\n		rmdir($dir);\r\n	}\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_set_impath() {\r\n	global $path_to_site, $img_dir;\r\n	if (!defined(\'IMPATH\')) {\r\n		define(\'IMPATH\', $path_to_site.DS.$img_dir.DS);\r\n	}\r\n}\r\n\r\n// ------------------------\r\n// Add thumbnail table if not already installed\r\n// Flags: Bit 0=Active / 1=Crop / 2 = Sharpen\r\nfunction smd_thumb_table_install() {\r\n	$GLOBALS[\'txp_err_count\'] = 0;\r\n	$ret = \'\';\r\n	$sql = array();\r\n	$sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.SMD_THUMB.\"` (\r\n		`name` varchar(32) NOT NULL default \'\',\r\n		`width` int(4) NOT NULL default \'0\',\r\n		`height` int(4) NOT NULL default \'0\',\r\n		`quality` tinyint(2) NULL default \'75\',\r\n		`flags` tinyint(2) NOT NULL default \'0\',\r\n		PRIMARY KEY (`name`)\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	// Backup the current thumb prefs\r\n	$exists = safe_row(\'*\', \'txp_prefs\', \"name=\'smd_thumb_backup_w\'\");\r\n	if (!$exists) {\r\n		set_pref(\'smd_thumb_backup_w\', get_pref(\'thumb_w\'), \'smd_thumb\', PREF_HIDDEN, \'text_input\', 0);\r\n		set_pref(\'smd_thumb_backup_h\', get_pref(\'thumb_h\'), \'smd_thumb\', PREF_HIDDEN, \'text_input\', 1);\r\n		set_pref(\'smd_thumb_backup_c\', get_pref(\'thumb_crop\'), \'smd_thumb\', PREF_HIDDEN, \'text_input\', 2);\r\n\r\n		// Erase the current thumb prefs\r\n		set_pref(\'thumb_w\', \'\', \'image\', PREF_HIDDEN);\r\n		set_pref(\'thumb_h\', \'\', \'image\', PREF_HIDDEN);\r\n		set_pref(\'thumb_crop\', \'\', \'image\', PREF_HIDDEN);\r\n	}\r\n}\r\n\r\n// ------------------------\r\n// Drop table if in database\r\nfunction smd_thumb_table_remove() {\r\n	$ret = \'\';\r\n	$sql = array();\r\n	$GLOBALS[\'txp_err_count\'] = 0;\r\n	if (smd_thumb_table_exist()) {\r\n		$sql[] = \"DROP TABLE IF EXISTS \" .PFX.SMD_THUMB. \"; \";\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	// Restore the original thumb prefs\r\n	$exists = safe_row(\'*\', \'txp_prefs\', \"name=\'smd_thumb_backup_w\'\");\r\n	if ($exists) {\r\n		set_pref(\'thumb_w\', get_pref(\'smd_thumb_backup_w\'), \'image\', PREF_HIDDEN);\r\n		set_pref(\'thumb_h\', get_pref(\'smd_thumb_backup_h\'), \'image\', PREF_HIDDEN);\r\n		set_pref(\'thumb_crop\', get_pref(\'smd_thumb_backup_c\'), \'image\', PREF_HIDDEN);\r\n\r\n	}\r\n	// Erase the backup thumb prefs\r\n	safe_delete(\'txp_prefs\', \"name IN(\'smd_thumb_backup_w\', \'smd_thumb_backup_h\', \'smd_thumb_backup_c\', \'smd_thumb_default_profile\', \'pane_smd_thumbnail_profiles_visible\', \'pane_smd_thumbnails_visible\')\");\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_table_exist($all=\'\') {\r\n	if ($all) {\r\n		$tbls = array(SMD_THUMB => 5);\r\n		$out = count($tbls);\r\n		foreach ($tbls as $tbl => $cols) {\r\n			if (gps(\'debug\')) {\r\n				echo \"++ TABLE \".$tbl.\" HAS \".count(@safe_show(\'columns\', $tbl)).\" COLUMNS; REQUIRES \".$cols.\" ++\".br;\r\n			}\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		if (gps(\'debug\')) {\r\n			echo \"++ TABLE \".SMD_THUMB.\" HAS \".count(@safe_show(\'columns\', SMD_THUMB)).\" COLUMNS;\";\r\n		}\r\n		return(@safe_show(\'columns\', SMD_THUMB));\r\n	}\r\n}\r\n\r\n// -------------------------------------------------------------\r\nfunction smd_thumbnail_save_pane_state() {\r\n	$panes = array(\'smd_thumbnail_profiles\', \'smd_thumbnails\');\r\n	$pane = gps(\'pane\');\r\n	if (in_array($pane, $panes))\r\n	{\r\n		set_pref(\"pane_{$pane}_visible\", (gps(\'visible\') == \'true\' ? \'1\' : \'0\'), \'smd_thumb\', PREF_HIDDEN, \'yesnoradio\', 0, PREF_PRIVATE);\r\n		send_xml_response();\r\n	} else {\r\n		send_xml_response(array(\'http-status\' => \'400 Bad Request\'));\r\n	}\r\n}\r\n\r\n//*****************\r\n// PUBLIC SIDE TAGS\r\n//*****************\r\n// ------------------------\r\nfunction smd_thumbnail($atts, $thing=NULL) {\r\n	global $thisimage, $img_dir;\r\n\r\n	extract(lAtts(array(\r\n		\'type\'       => get_pref(\'smd_thumb_default_profile\', \'\'),\r\n		\'id\'         => \'\',\r\n		\'name\'       => \'\',\r\n		\'escape\'     => \'html\',\r\n		\'wraptag\'    => \'\',\r\n		\'class\'      => \'\',\r\n		\'html_id\'    => \'\',\r\n		\'style\'      => \'\',\r\n		\'link\'       => \'\',\r\n		\'link_rel\'   => \'\',\r\n		\'poplink\'    => 0,\r\n		\'add_stamp\'  => 0,\r\n		\'width\'      => \'\',\r\n		\'height\'     => \'\',\r\n		\'force_size\' => \'\',\r\n		\'display\'    => \'thumbnail\', // thumbnail (full img tag) or url\r\n		\'form\'       => \'\',\r\n	),$atts));\r\n\r\n	$thing = (empty($form)) ? $thing : fetch_form($form);\r\n\r\n	if ($name) {\r\n		$name = doSlash($name);\r\n		$rs = safe_row(\'*\', \'txp_image\', \"name = \'$name\' limit 1\");\r\n	} elseif ($id) {\r\n		$id = (int) $id;\r\n		$rs = safe_row(\'*\', \'txp_image\', \"id = $id limit 1\");\r\n	} elseif ($thisimage) {\r\n		$id = (int) $thisimage[\'id\'];\r\n		$rs = $thisimage;\r\n	} else {\r\n		trigger_error(gTxt(\'unknown_image\'));\r\n		return;\r\n	}\r\n\r\n	smd_thumb_set_impath();\r\n\r\n	if ($rs) {\r\n		extract($rs);\r\n		$thumbinfo = safe_row(\'*\', SMD_THUMB, \"name=\'\".doSlash($type).\"\'\");\r\n		if ($thumbinfo) {\r\n			$dir = sanitizeForUrl($thumbinfo[\'name\']);\r\n			$path = IMPATH.$dir.DS.$id.$ext;\r\n			if (file_exists($path)) {\r\n				// Drop out if all we need to display is the thumb\'s URL\r\n				if ($display == \'url\') {\r\n					return ihu.$img_dir.\'/\'.$dir.\'/\'.$id.$ext;\r\n				}\r\n				if ($escape == \'html\') {\r\n					$alt = htmlspecialchars($alt);\r\n					$caption = htmlspecialchars($caption);\r\n				}\r\n\r\n				$meta[\'url\'] = ihu.$img_dir.\'/\'.$dir.\'/\'.$id.$ext;\r\n				$meta[\'alt\'] = $alt;\r\n				$meta[\'type\'] = $type;\r\n				$meta[\'width\'] = $width;\r\n				$meta[\'height\'] = $height;\r\n				$force_size = do_list($force_size);\r\n				if (in_array(\'width\', $force_size)) {\r\n					$meta[\'forcew\'] = 1;\r\n				}\r\n				if (in_array(\'height\', $force_size)) {\r\n					$meta[\'forceh\'] = 1;\r\n				}\r\n				if ($add_stamp == 0) { $meta[\'stamp\'] = 1; } // Negative logic since we want the stamp on by default for the admin side\r\n				if ($name) { $meta[\'name\'] = $name; }\r\n				if ($caption) { $meta[\'title\'] = $caption; }\r\n// $meta[\'title\'] = gTxt(\'smd_thumb_image\') . $rs[\'w\'] . \" &#215; \" . $rs[\'h\'] . sp .  gTxt(\'smd_thumb_profile\') . $thumbinfo[\'width\'] . \" &#215; \" . $thumbinfo[\'height\'];\r\n\r\n				if ($category) {\r\n					$meta[\'category\'] = $category;\r\n					$meta[\'category_title\'] = fetch_category_title($category, \'image\');\r\n				}\r\n				if ($author) { $meta[\'author\'] = $author; }\r\n				if ($date) { $meta[\'date\'] = $date; }\r\n				if ($html_id && !$wraptag) { $meta[\'id\'] = $html_id; }\r\n				if ($class && !$wraptag) { $meta[\'class\'] = $class; } else { $class=$type; }\r\n				if ($style) { $meta[\'style\'] = $style; }\r\n\r\n				$out = smd_thumb_img($thumbinfo, $rs, $meta, $thing);\r\n\r\n				if ($link) {\r\n					$out = href($out, imagesrcurl($id, $ext), (!empty($link_rel) ? \' rel=\"\'.$link_rel.\'\"\' : \'\').\' title=\"\'.$caption.\'\"\');\r\n				} elseif ($poplink) {\r\n					$out = \'<a href=\"\'.imagesrcurl($id, $ext).\'\"\'.\r\n						\' onclick=\"window.open(this.href, \\\'popupwindow\\\', \'.\r\n						\'\\\'width=\'.$w.\', height=\'.$h.\', scrollbars, resizable\\\'); return false;\">\'.$out.\'</a>\';\r\n				}\r\n\r\n				return ($wraptag) ? doTag($out, $wraptag, $class, \'\', $html_id) : $out;\r\n			}\r\n		}\r\n	}\r\n\r\n	trigger_error(gTxt(\'unknown_image\'));\r\n}\r\n\r\n// ------------------------\r\nfunction smd_if_thumbnail($atts, $thing) {\r\n	global $thisimage;\r\n	assert_image();\r\n\r\n	smd_thumb_set_impath();\r\n\r\n	extract(lAtts(array(\r\n		\'type\' => @get_pref(\'smd_thumb_default_profile\'),\r\n	),$atts));\r\n\r\n	$thumbinfo = safe_row(\'*\', SMD_THUMB, \"name=\'\".doSlash($type).\"\'\");\r\n	$ret = false;\r\n	if ($thumbinfo) {\r\n		$path = IMPATH.sanitizeForUrl($thumbinfo[\'name\']).DS.$thisimage[\'id\'].$thisimage[\'ext\'];\r\n		$ret = file_exists($path);\r\n	}\r\n	return parse(EvalElse($thing, $ret));\r\n}\r\n\r\n// Display data for form/container usage\r\nfunction smd_thumbnail_info($atts, $thing=NULL) {\r\n	global $smd_thumb_data;\r\n\r\n	extract(lAtts(array(\r\n		\'item\'    => \'\',\r\n		\'wraptag\' => \'\',\r\n		\'break\'   => \'\',\r\n		\'class\'   => \'\',\r\n		\'debug\'   => 0,\r\n	), $atts));\r\n\r\n	$tdata = is_array($smd_thumb_data) ? $smd_thumb_data : array();\r\n\r\n	if ($debug) {\r\n		echo \'++ AVAILABLE INFO ++\';\r\n		dmp($tdata);\r\n	}\r\n\r\n	$items = do_list($item);\r\n	$out = array();\r\n	foreach ($items as $it) {\r\n		if (isset($tdata[$it])) {\r\n			$out[] = $tdata[$it];\r\n		}\r\n	}\r\n\r\n	return doWrap($out, $wraptag, $break, $class);\r\n}\r\n','/**\r\n * smd_thumbnail\r\n *\r\n * A Textpattern CMS plugin for managing multiple image thumbnails:\r\n *  -> Create unlimited thumbnail profiles of differing sizes for various site uses\r\n *  -> Thumbnail files are created in your image directory -- no realtime scaling\r\n *  -> Batch create / alter thumbnails\r\n *  -> Choose to sync the thumbnails with Textpattern\'s own thumbs\r\n *\r\n * @author Stef Dawson\r\n * @link   http://stefdawson.com/\r\n * TODO: Make Selections (lines 912-916) optional via prefs or something\r\n * TODO: Simplify AJAX response packets to speed things up\r\n * TODO: Interface elements for Profile creation don\'t get rendered if there are no images in pool\r\n */\r\nif (@txpinterface == \'admin\') {\r\n	global $smd_thumb_event, $smd_thumb_prevs;\r\n\r\n	$smd_thumb_event = \'smd_thumbnail\';\r\n	$smd_thumb_privs = \'1,2,3\'; // Plugin privs\r\n	$smd_thumb_prevs = array(\'1\'); // Privs for prefs\r\n\r\n	add_privs($smd_thumb_event, $smd_thumb_privs);\r\n	add_privs(\'smd_thumb_profiles\', $smd_thumb_privs);\r\n	register_callback(\'smd_thumb_welcome\', \'plugin_lifecycle.\'.$smd_thumb_event);\r\n	register_callback(\'smd_thumb_profiles\', \'image_ui\', \'extend_controls\');\r\n	register_callback(\'smd_thumbs\', \'image_ui\', \'thumbnail\');\r\n	register_callback(\'smd_thumb_edit\', \'image_ui\', \'thumbnail_edit\');\r\n	register_callback(\'smd_thumb_empty\', \'image_ui\', \'thumbnail_image\');\r\n	register_callback(\'smd_thumb_empty\', \'image_ui\', \'thumbnail_create\');\r\n	register_callback(\'smd_thumb_generate\', \'image_uploaded\', \'image\');\r\n	register_callback(\'smd_thumb_generate\', \'image_uploaded\', \'moderate\');\r\n	register_callback(\'smd_thumb_delete\', \'image_deleted\', \'image\');\r\n	register_callback(\'smd_thumb_create_one\', \'image\', \'smd_thumb_create_one\');\r\n	register_callback(\'smd_thumb_switch_active\', \'image\', \'smd_thumb_switch_active\');\r\n	register_callback(\'smd_thumb_switch_pref\', \'image\', \'smd_thumb_switch_pref\');\r\n	register_callback(\'smd_thumb_insert\', \'image\', \'smd_thumbnail_insert\', 1);\r\n	register_callback(\'smd_thumb_inject_css\', \'admin_side\', \'head_end\');\r\n} else {\r\n	smd_thumb_set_impath();\r\n}\r\nif (!defined(\'SMD_THUMB\')) {\r\n	define(\"SMD_THUMB\", \'smd_thumbnail\');\r\n}\r\nif (!defined(\'SMD_THUMB_ACTIVE\')) {\r\n	define(\"SMD_THUMB_ACTIVE\", 1);\r\n}\r\nif (!defined(\'SMD_THUMB_CROP\')) {\r\n	define(\"SMD_THUMB_CROP\", 2);\r\n}\r\nif (!defined(\'SMD_THUMB_SHARP\')) {\r\n	define(\"SMD_THUMB_SHARP\", 4);\r\n}\r\n\r\n// -------------------------------------------------------------\r\n// CSS definitions: hopefully kind to themers\r\nfunction smd_thumb_get_style_rules() {\r\n	$smd_thumb_styles = array(\r\n		\'smd_thumb\' =>\'\r\n.smd_selected { border:1px solid red; background:red; }\r\n#smd_thumbs img { padding:2px; margin:1px; border:1px solid black; }\r\n.thumbnail-alter { display:none; }\r\n.smd_hidden { display:none; }\r\n#smd_thumb_profiles { max-width:700px; margin:0 auto 18px; }\r\n#smd_thumb_profiles .txp-summary { text-align:left; }\r\n#smd_thumb_batch { float:right; }\r\n.smd_inactive { opacity:.3; }\r\n.smd_thumb_heading_active { cursor:pointer; }\r\n#smd_thumb_profile_form table { width:100% }\r\n.pref-label { text-align: right!important; }\r\n\'\r\n);\r\n\r\n	return $smd_thumb_styles;\r\n}\r\n\r\n// -------------------------------------------------------------\r\nfunction smd_thumb_inject_css($evt, $stp) {\r\n	global $event;\r\n\r\n	if ($event == \'image\') {\r\n		$smd_thumb_styles = smd_thumb_get_style_rules();\r\n		echo \'<style type=\"text/css\">\', $smd_thumb_styles[\'smd_thumb\'], \'</style>\';\r\n	}\r\n\r\n	return;\r\n}\r\n\r\n// -------------------------------------------------------------\r\nfunction smd_thumb_welcome($evt, $stp) {\r\n	switch ($stp) {\r\n		case \'installed\':\r\n			smd_thumb_table_install();\r\n			// Remove per-user prefs on upgrade from v0.1x to v0.20\r\n			safe_delete (\'txp_prefs\', \"name IN (\'smd_thumb_txp_create\', \'smd_thumb_txp_delete\', \'smd_thumb_auto_replace\') AND user_name != \'\'\");\r\n			break;\r\n		case \'deleted\':\r\n			smd_thumb_table_remove();\r\n			break;\r\n	}\r\n	return;\r\n}\r\n\r\n// ------------------------\r\n// Display the designated default thumbnail on the list page\r\nfunction smd_thumbs($evt, $stp, $dflt, $currimg) {\r\n	extract(gpsa(array(\'page\', \'sort\', \'dir\', \'crit\', \'search_method\')));\r\n\r\n	if (smd_thumb_table_exist()) {\r\n		$default = get_pref(\'smd_thumb_default_profile\', \'\', 1);\r\n		if ($default) {\r\n			$row = safe_row(\'*\', SMD_THUMB, \"name=\'\".sanitizeForUrl($default).\"\'\");\r\n			if ($row) {\r\n				$edit_url = \'?event=image\'.a.\'step=image_edit\'.a.\'id=\'.$currimg[\'id\'].a.\'sort=\'.$sort.\r\n						a.\'dir=\'.$dir.a.\'page=\'.$page.a.\'search_method=\'.$search_method.a.\'crit=\'.$crit;\r\n				$out = smd_thumb_img($row, $currimg);\r\n				return ($out) ? href($out, $edit_url) : gTxt(\'no\');\r\n			} else {\r\n				return gTxt(\'no\');\r\n			}\r\n		} else {\r\n			return gTxt(\'no\');\r\n		}\r\n	}\r\n}\r\n\r\n// ------------------------\r\n// Don\'t want the \'create\' controls or thumbnail image as they\'re now both handled in the edit portion of the screen\r\nfunction smd_thumb_empty($evt, $stp, $dflt, $currimg) {\r\n	return \' \';\r\n}\r\n\r\n// ------------------------\r\n// With a lot of images in the database, doing it all in one hit would time out.\r\n// So it\'s done via ajax; one request per image\r\nfunction smd_thumb_create_group($type, $lst=\'\') {\r\n	switch ($type) {\r\n		case \'all\':\r\n			$where = \'1=1\';\r\n			break;\r\n		case \'sel\':\r\n			$where = ($lst) ? \'id IN (\'.doSlash($lst).\')\' : \'1=0\';\r\n			break;\r\n		case \'cat\':\r\n			$where = \"category in (\'\".doSlash($lst).\"\')\";\r\n			break;\r\n		case \'usr\':\r\n			$where = \"author in (\'\".doSlash($lst).\"\')\";\r\n			break;\r\n	}\r\n\r\n	$images = safe_column(\'id\', \'txp_image\', $where);\r\n	$count = count($images);\r\n\r\n	echo \'<script type=\"text/javascript\">\';\r\n	echo \"var bctr = 0; var btot = {$count};\";\r\n	echo \'jQuery(function() {\';\r\n	echo \'jQuery(\"#smd_thumb_btot\").text(\"/\'.$count.\'\");\';\r\n	foreach ($images as $img) {\r\n		echo <<<EOJS\r\n		sendAsyncEvent(\r\n		{\r\n			event: textpattern.event,\r\n			step: \'smd_thumb_create_one\',\r\n			smd_thumb_imgid: {$img}\r\n		}, smd_create_group_feedback);\r\nEOJS;\r\n	}\r\n	echo \'});\';\r\n	echo <<<EOJS\r\n	function smd_create_group_feedback() {\r\n		bctr++;\r\n		jQuery(\'#smd_thumb_bcurr\').text(bctr);\r\n//		if (bctr >= btot) {\r\n//			jQuery(\'#smd_thumb_bcurr,#smd_thumb_btot\').empty();\r\n//		}\r\n	}\r\nEOJS;\r\n	echo \'</script>\';\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_create_one() {\r\n	$currimg = gps(\'smd_thumb_imgid\');\r\n	assert_int($currimg);\r\n\r\n	$rs = safe_rows(\'*\', SMD_THUMB, \'1=1 AND flags & \'.SMD_THUMB_ACTIVE);\r\n	$curr = safe_row(\'*\', \'txp_image\', \"id=\".doSlash($currimg));\r\n	if ($rs) {\r\n		smd_thumb_make($rs, $curr, 1);\r\n		send_xml_response();\r\n	}\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_switch_active() {\r\n	$name = doSlash(gps(\'smd_thumb_profile\'));\r\n	if ($name) {\r\n		safe_update(SMD_THUMB, \'flags = flags ^ \'.SMD_THUMB_ACTIVE, \"name=\'$name\'\");\r\n		send_xml_response(array(\'smd_thumb_profile\' => $name));\r\n	}\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_switch_pref() {\r\n	$name = doSlash(gps(\'smd_thumb_txptype\'));\r\n	$state = doSlash(gps(\'smd_thumb_state\'));\r\n	if ($name) {\r\n		set_pref(\'smd_thumb_\'.$name, $state, \'smd_thumb\', PREF_HIDDEN, \'text_input\');\r\n		send_xml_response();\r\n	}\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_generate($evt, $stp, $id=\'\') {\r\n	$id = ($id) ? $id : gps(\'id\');\r\n	$id = ($id) ? $id : $GLOBALS[\'ID\'];\r\n	$rs = safe_rows(\'*\', SMD_THUMB, \'1=1 AND flags & \'.SMD_THUMB_ACTIVE);\r\n\r\n	smd_thumb_make($rs, $id);\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_delete($evt, $stp) {\r\n	$ids = gps(\'selected\');\r\n	$rs = safe_rows(\'*\', SMD_THUMB, \'1=1\');\r\n\r\n	$images = safe_rows(\'*\', \'txp_image\', \'id IN (\' . join(\',\',quote_list($ids)) . \')\');\r\n	foreach ($images as $img) {\r\n		smd_thumb_unmake($rs, $img);\r\n	}\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_make($rs, $currimg, $force=0) {\r\n	// Wrapper for wet_thumb to allow multiple thumbnails\r\n	$msg = \'\';\r\n	smd_thumb_set_impath();\r\n\r\n	if (!class_exists(\'smd_thumb\')) {\r\n		class smd_thumb extends wet_thumb {\r\n			var $m_ext;\r\n			var $m_id;\r\n			var $m_dir;\r\n			var $m_dflt;\r\n			var $width;\r\n			var $height;\r\n			var $force;\r\n\r\n			// Constructor\r\n			public function __construct ($id, $dir, $img_row, $is_default=false, $pro_w=\'\', $pro_h=\'\', $force=0) {\r\n				$id = assert_int($id);\r\n				if ($img_row) {\r\n					extract($img_row);\r\n					$this->m_id = $id;\r\n					$this->m_ext = $ext;\r\n					$this->m_dir = $dir;\r\n					$this->force = $force;\r\n					$this->width = $pro_w;\r\n					$this->height = $pro_h;\r\n					$this->m_dflt = $is_default;\r\n				}\r\n				parent::__construct();\r\n			}\r\n\r\n			public function write_image() {\r\n				if ( !isset($this->m_ext) ) return false;\r\n\r\n				$autor = get_pref(\'smd_thumb_auto_replace\', \'0\');\r\n				$recfrom = get_pref(\'smd_thumb_create_from\', \'full\');\r\n				$src_sz = ($recfrom == \'full\') ? \'\' : \'t\';\r\n				$infile = IMPATH.$this->m_id.$src_sz.$this->m_ext;\r\n				$outfile = IMPATH.$this->m_dir.DS.$this->m_id.$this->m_ext;\r\n\r\n				if (!file_exists($outfile) || $autor || $this->force) {\r\n					// If this is the default profile and the pref indicates, write a Txp thumb too\r\n					if (($this->m_dflt == true) && (get_pref(\'smd_thumb_txp_create\', \'0\'))) {\r\n						$txp_thumb = IMPATH.$this->m_id.\'t\'.$this->m_ext;\r\n						if ( parent::write ( $infile, $txp_thumb ) ) {\r\n							safe_update(\'txp_image\', \"thumbnail = 1, thumb_w = $this->width, thumb_h = $this->height\", \'id = \'.$this->m_id);\r\n							@chmod($outfile, 0644);\r\n						}\r\n					}\r\n	\r\n					if ( parent::write ( $infile, $outfile ) ) {\r\n						@chmod($outfile, 0644);\r\n						return true;\r\n					}\r\n				}\r\n				return false;\r\n			}\r\n		}\r\n	}\r\n\r\n	// If passed only an ID, look up the rest of the image data\r\n	if (!is_array($currimg)) {\r\n		assert_int($currimg);\r\n		$currimg = safe_row(\'*\', \'txp_image\', \'id=\'.$currimg);\r\n	}\r\n\r\n	// Create each thumbnail\r\n	$pro_dflt = get_pref(\'smd_thumb_default_profile\', \'\');\r\n	foreach ($rs as $row) {\r\n		// Sanitize a little\r\n		$width = (int) $row[\'width\'];\r\n		$height = (int) $row[\'height\'];\r\n		if ($width == 0) $width = \'\';\r\n		if ($height == 0) $height = \'\';\r\n		if ($width === \'\' && $height === \'\') continue;\r\n		$crop = ($row[\'flags\'] & SMD_THUMB_CROP) ? 1 : 0;\r\n		$sharpen = ($row[\'flags\'] & SMD_THUMB_SHARP) ? 1 : 0;\r\n		$id = $currimg[\'id\'];\r\n		$is_dflt = ($row[\'name\'] == $pro_dflt) ? true : false;\r\n\r\n		$t = new smd_thumb($id, sanitizeForUrl($row[\'name\']), $currimg, $is_dflt, $width, $height, $force);\r\n		$t->extrapolate = true; // Allow bigger thumbs than original image\r\n		$t->crop = ($crop == \'1\');\r\n		$t->sharpen = ($sharpen == \'1\');\r\n		$t->hint = \'0\';\r\n		$t->width = $width;\r\n		$t->height = $height;\r\n		$t->quality = $row[\'quality\'];\r\n\r\n		if ($t->write_image()) {\r\n			$msg = gTxt(\'thumbnail_saved\', array(\'{id}\' => $id));\r\n		} else {\r\n			$msg = array(gTxt(\'thumbnail_not_saved\', array(\'{id}\' => $id)), E_ERROR);\r\n		}\r\n	}\r\n	return $msg;\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_unmake($rs, $currimg) {\r\n	$id = $currimg[\'id\'];\r\n	$ext = $currimg[\'ext\'];\r\n	$pro_dflt = get_pref(\'smd_thumb_default_profile\', \'\');\r\n	$txp_del = get_pref(\'smd_thumb_txp_delete\', \'0\');\r\n	smd_thumb_set_impath();\r\n\r\n	foreach ($rs as $row) {\r\n		$path = IMPATH.sanitizeForUrl($row[\'name\']).DS.$id.$ext;\r\n		if (file_exists($path)) {\r\n			unlink($path);\r\n		}\r\n\r\n		// Also remove Txp\'s built-in thumb?\r\n		if (($row[\'name\'] == $pro_dflt) && ($txp_del == \'1\')) {\r\n			$path = IMPATH.$id.\'t\'.$ext;\r\n			if (file_exists($path)) {\r\n				safe_update(\'txp_image\', \"thumbnail = 0, thumb_w = 0, thumb_h = 0\", \'id = \'.$id);\r\n				unlink($path);\r\n			}\r\n		}\r\n	}\r\n	return \'\';\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_insert() {\r\n	global $txpcfg, $txp_user, $page, $sort, $dir, $crit, $search_method;\r\n\r\n	smd_thumb_set_impath();\r\n	extract(gpsa(array(\'page\', \'sort\', \'dir\', \'crit\', \'search_method\')));\r\n\r\n	include_once txpath.\'/lib/txplib_misc.php\';\r\n\r\n	extract($txpcfg);\r\n	$id = assert_int(gps(\'id\'));\r\n	$profile = gps(\'smd_thumb_profile\');\r\n	$thumb_ext = gps(\'smd_thumb_ext\');\r\n\r\n	$author = fetch(\'author\', \'txp_image\', \'id\', $id);\r\n	if (!has_privs(\'image.edit\') && !($author == $txp_user && has_privs(\'image.edit.own\'))) {\r\n		return;\r\n	}\r\n\r\n	$file = $_FILES[\'thefile\'][\'tmp_name\'];\r\n	$name = $_FILES[\'thefile\'][\'name\'];\r\n\r\n	$file = get_uploaded_file($file);\r\n\r\n	if (empty($file))\r\n	{\r\n		return;\r\n	}\r\n\r\n	list($w, $h, $extension) = getimagesize($file);\r\n	$valid_exts = array(\r\n		IMAGETYPE_GIF  => \'.gif\',\r\n		IMAGETYPE_JPEG => \'.jpg\',\r\n		IMAGETYPE_PNG  => \'.png\',\r\n		IMAGETYPE_SWF  => \'.swf\',\r\n	);\r\n	$ext = isset($valid_exts[$extension]) ? $valid_exts[$extension] : \'\';\r\n\r\n	if (($file !== false) && $profile && $ext) {\r\n		$newpath = IMPATH.sanitizeForUrl($profile).DS.$id.$ext;\r\n		if (shift_uploaded_file($file, $newpath) == false) {\r\n		} else {\r\n			@chmod($newpath, 0644);\r\n\r\n			// If the pref indicates, duplicate as a Txp thumb too\r\n			if (get_pref(\'smd_thumb_txp_create\', \'0\') == 1) {\r\n				$txp_thumb = IMPATH.$id.\'t\'.$ext;\r\n				if ( copy ( $newpath, $txp_thumb ) ) {\r\n					safe_update(\'txp_image\', \"thumbnail = 1, thumb_w = $w, thumb_h = $h\", \'id = \'.$id);\r\n					@chmod($txp_thumb, 0644);\r\n				}\r\n			}\r\n			$message = gTxt(\'image_uploaded\', array(\'{name}\' => $name));\r\n		}\r\n	}\r\n\r\n	// Since the headers have been sent, resort to js to refresh the page\r\n	$url = \'?event=image\'.a.\'step=image_edit\'.a.\'id=\'.$id.a.\'sort=\'.$sort.a.\'dir=\'.$dir.a.\'page=\'.$page.a.\'search_method=\'.$search_method.a.\'crit=\'.$crit;\r\n	echo <<<EOS\r\n<script type=\"text/javascript\">\r\nwindow.location.href=\"{$url}\";\r\n</script>\r\n<noscript>\r\n<meta http-equiv=\"refresh\" content=\"0;url={$url}\" />\r\n</noscript>\r\nEOS;\r\n	exit;\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_edit($evt, $stp, $dflt, $currimg) {\r\n	global $step, $file_max_upload_size, $txp_user;\r\n\r\n	extract(gpsa(array(\r\n		\'id\',\r\n		\'page\',\r\n		\'sort\',\r\n		\'dir\',\r\n		\'crit\',\r\n		\'search_method\',\r\n		\'smd_thumbnail_size\',\r\n		\'smd_thumbnail_chosen_size\',\r\n		\'smd_thumbnail_delete\',\r\n		\'smd_step\'\r\n	)));\r\n\r\n	$id = ($id) ? $id : $GLOBALS[\'ID\'];\r\n\r\n	// Toggle profile panel\r\n	if ($step == \'save_pane_state\') {\r\n		smd_thumbnail_save_pane_state();\r\n		return;\r\n	}\r\n\r\n	// Create/delete the selected thumbs depending on the button pressed\r\n	if ($smd_step == \'smd_thumbnail_manage\') {\r\n		// Validate user\r\n		$author = fetch(\'author\', \'txp_image\', \'id\', $id);\r\n		if (!has_privs(\'image.edit\') && !($author == $txp_user && has_privs(\'image.edit.own\')))\r\n		{\r\n			image_list(gTxt(\'restricted_area\'));\r\n			return;\r\n		}\r\n\r\n		// Grab the thumbnails to work on\r\n		$where = ($smd_thumbnail_size==\'all\') ? \'1=1 AND flags & \'.SMD_THUMB_ACTIVE : \"name=\'\".doSlash($smd_thumbnail_chosen_size).\"\'\";\r\n		$rs = safe_rows(\'*\', SMD_THUMB, $where);\r\n\r\n		// Do it\r\n		if($smd_thumbnail_delete) {\r\n			$msg = smd_thumb_unmake($rs, $currimg);\r\n		} else {\r\n			$msg = smd_thumb_make($rs, $currimg, 1);\r\n		}\r\n	}\r\n\r\n	$ext = $currimg[\'ext\'];\r\n	echo script_js(<<<EOC\r\nfunction smd_thumb_selector(sel) {\r\n	var idx = 0;\r\n	jQuery(\"#smd_thumbs img\").each(function() {\r\n		if (jQuery(this).hasClass(sel)) {\r\n			jQuery(this).toggleClass(\'smd_selected\');\r\n			if (jQuery(this).hasClass(\'smd_selected\')) {\r\n				jQuery(\"#smd_upload_thumbnail\").attr(\'disabled\', false);\r\n				jQuery(\"#smd_thumb_profile\").val(sel);\r\n				idx = jQuery(\"#smd_thumbnail_size option[value=\'\"+sel+\"\']\").index();\r\n				jQuery(\"#smd_thumbnail_chosen_size\").val(sel);\r\n			} else {\r\n				jQuery(\"#smd_upload_thumbnail\").attr(\'disabled\', true);\r\n				jQuery(\"#smd_thumb_profile\").val(\'\');\r\n				jQuery(\"#smd_thumbnail_chosen_size\").val(\'\');\r\n			}\r\n		} else {\r\n			jQuery(this).removeClass(\'smd_selected\');\r\n		}\r\n	});\r\n	jQuery(\"#smd_thumbnail_size\").prop(\"selectedIndex\", idx);\r\n}\r\nfunction smd_thumb_select_changed() {\r\n	obj = jQuery(\"#smd_thumbnail_size\");\r\n	if (obj.attr(\"selectedIndex\") == 0) {\r\n		jQuery(\"#smd_upload_thumbnail\").attr(\'disabled\', true);\r\n		jQuery(\"#smd_thumb_profile\").val(\'\');\r\n		jQuery(\"#smd_thumbnail_chosen_size\").val(\'\');\r\n	} else {\r\n		jQuery(\"#smd_upload_thumbnail\").attr(\'disabled\', false);\r\n		jQuery(\"#smd_thumb_profile\").val(obj.val());\r\n		jQuery(\"#smd_thumbnail_chosen_size\").val(obj.val());\r\n	}\r\n	jQuery(\"#smd_thumbs img\").each(function() {\r\n		if (jQuery(this).hasClass(obj.val())) {\r\n			jQuery(this).addClass(\'smd_selected\');\r\n		} else {\r\n			jQuery(this).removeClass(\'smd_selected\');\r\n		}\r\n	});\r\n}\r\njQuery(function() {\r\n	jQuery(\"#smd_thumbs img\").each(function() {\r\n		var cls = jQuery(this).attr(\'class\');\r\n		jQuery(this).click(function() {\r\n			smd_thumb_selector(cls);\r\n		});\r\n	});\r\n	jQuery(\"#smd_upload_thumbnail\").attr(\'disabled\', true);\r\n	jQuery(\".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}\" />\');\r\n});\r\nEOC\r\n	);\r\n\r\n	// Add thumbnails and creation controls\r\n	if (smd_thumb_table_exist()) {\r\n		$rs = safe_rows(\'*\', SMD_THUMB, \'1=1 ORDER BY name\');\r\n		if ($rs) {\r\n			$profiles = array(\'all\' => gTxt(\'smd_thumb_all_sizes\'));\r\n			$thumbs[] = \'<div id=\"smd_thumbs\">\';\r\n			foreach ($rs as $row) {\r\n				if ($row[\'flags\'] & SMD_THUMB_ACTIVE) {\r\n					$profiles[$row[\'name\']] = $row[\'name\'];\r\n				}\r\n				$thumbs[] = smd_thumb_img($row, $currimg);\r\n			}\r\n			$thumbs[] = \'</div>\';\r\n			$qs = array(\r\n				\"event\" => \'image\',\r\n				\"step\" => \'image_edit\',\r\n				\"id\" => $id,\r\n				\"page\" => $page,\r\n				\"sort\" => $sort,\r\n				\"dir\" => $dir,\r\n				\"crit\" => $crit,\r\n				\"search_method\" => $search_method,\r\n			);\r\n\r\n			$out[] = \'<div id=\"smd_thumbnail_control\"><span class=\"txp-summary lever\'.(get_pref(\'pane_smd_thumbnails_visible\') ? \' expanded\' : \'\').\'\"><a href=\"#smd_thumbnails\">\'.gTxt(\'smd_thumb_thumbnail_heading\').\'</a></span><div id=\"smd_thumbnails\" class=\"toggle\" style=\"display:\'.(get_pref(\'pane_smd_thumbnails_visible\') ? \'block\' : \'none\').\'\">\';\r\n			$out[] = \'<form name=\"smd_thumbnail_create\" method=\"post\" action=\"\'.join_qs($qs).\'\">\';\r\n			$out[] = fInput(\'hidden\', \'smd_step\', \'smd_thumbnail_manage\');\r\n			$out[] = fInput(\'hidden\', \'smd_thumbnail_chosen_size\', \'\', \'\', \'\', \'\', \'\', \'\', \'smd_thumbnail_chosen_size\');\r\n			$out[] = selectInput(\'smd_thumbnail_size\', $profiles, \'\', \'\', \' onchange=\"return smd_thumb_select_changed()\";\', \'smd_thumbnail_size\');\r\n			$out[] = fInput(\'submit\', \'\', gTxt(\'create\'));\r\n			$out[] = fInput(\'submit\', \'smd_thumbnail_delete\', gTxt(\'delete\'));\r\n			$out[] = join(\'\', $thumbs);\r\n			$out[] = \'</form>\';\r\n			$out[] = upload_form(gTxt(\'smd_thumb_upload\'), \'upload_thumbnail\', \'smd_thumbnail_insert\', \'image\', $id, $file_max_upload_size, \'smd_upload_thumbnail\', \'thumbnail-upload\');\r\n			$out[] = \'</div>\';\r\n			return join(\'\', $out);\r\n		}\r\n	}\r\n	return \' \';\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_img($row, $currimg, $meta=array(), $dsp=\'\') {\r\n	global $img_dir, $smd_thumb_data;\r\n\r\n	smd_thumb_set_impath();\r\n\r\n	$dir = sanitizeForUrl($row[\'name\']);\r\n	$id = $currimg[\'id\'];\r\n	$ext = $currimg[\'ext\'];\r\n	// alt is a mandatory attribute so make sure it exists (even if it =\"\")\r\n	if (!isset($meta[\'alt\'])) {\r\n		$meta[\'alt\'] = $currimg[\'alt\'];\r\n	}\r\n\r\n	$path = IMPATH.$dir.DS.$id.$ext;\r\n\r\n	if (file_exists($path)) {\r\n		$extras = \'\';\r\n		if (isset($meta[\'forcew\']) || isset($meta[\'forceh\'])) {\r\n			$dims = getimagesize($path);\r\n			if (isset($meta[\'forcew\']) && !$row[\'width\']) {\r\n				$row[\'width\'] = $dims[0];\r\n			}\r\n			if (isset($meta[\'forceh\']) && !$row[\'height\']) {\r\n				$row[\'height\'] = $dims[1];\r\n			}\r\n			unset($meta[\'forcew\']);\r\n			unset($meta[\'forceh\']);\r\n		}\r\n\r\n		if (isset($meta[\'width\']) && $meta[\'width\'] !== \'\') {\r\n			$row[\'width\'] = $meta[\'width\'];\r\n		}\r\n		if (isset($meta[\'height\']) && $meta[\'height\'] !== \'\') {\r\n			$row[\'height\'] = $meta[\'height\'];\r\n		}\r\n		unset($meta[\'width\']);\r\n		unset($meta[\'height\']);\r\n\r\n		// \'empty\' includes zero dimensions which omits the attribute and allows the browser to scale\r\n		$w = (!empty($row[\'width\'])) ? \' width=\"\'.$row[\'width\'].\'\"\': \'\';\r\n		$h = (!empty($row[\'height\'])) ? \' height=\"\'.$row[\'height\'].\'\"\': \'\';\r\n\r\n		$uDate = \'\';\r\n		if (!isset($meta[\'stamp\'])) {\r\n			$uDate = \'?\'.filemtime($path);\r\n		}\r\n		unset($meta[\'stamp\']);\r\n\r\n		if (!isset($meta[\'class\'])) {\r\n			$meta[\'class\'] = $dir;\r\n		}\r\n\r\n		foreach ($meta as $key => $val) {\r\n			// We need all atts for container tags, but only valid HTML atts should appear in the default <img> tag\r\n			if (in_array($key, array(\'alt\', \'class\', \'title\'))) {\r\n				$extras .= \' \'.$key.\'=\"\'.$val.\'\"\';\r\n			}\r\n		}\r\n\r\n		if ($dsp) {\r\n			// Hand off to the form/container for formatting advice\r\n			$smd_thumb_data = $meta;\r\n			$smd_thumb_data[\'id\'] = $id;\r\n			$smd_thumb_data[\'ext\'] = $ext;\r\n			$smd_thumb_data[\'alt\'] = $alt;\r\n			$smd_thumb_data[\'w\'] = $row[\'width\'];\r\n			$smd_thumb_data[\'h\'] = $row[\'height\'];\r\n			$smd_thumb_data[\'html_w\'] = $w;\r\n			$smd_thumb_data[\'html_h\'] = $h;\r\n			return parse($dsp);\r\n		} else {\r\n			return \'<img src=\"\'.ihu.$img_dir.\'/\'.$dir.\'/\'.$id.$ext.$uDate.\'\"\'.$w.$h.$extras.\' />\';\r\n		}\r\n	}\r\n\r\n	return \'\';\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_profiles($evt, $stp, $dflt, $imglist) {\r\n	global $smd_thumb_event, $step, $smd_thumb_prevs, $txp_user;\r\n\r\n	if (!has_privs(__FUNCTION__)) {\r\n		return;\r\n	}\r\n\r\n	extract(gpsa(array(\r\n		\'page\',\r\n		\'sort\',\r\n		\'dir\',\r\n		\'crit\',\r\n		\'search_method\',\r\n		\'smd_thumb_add\',\r\n		\'smd_thumb_cancel\',\r\n		\'smd_thumb_save\',\r\n		\'smd_thumb_name\',\r\n		\'smd_thumb_newname\',\r\n		\'smd_thumb_width\',\r\n		\'smd_thumb_height\',\r\n		\'smd_thumb_quality\',\r\n		\'smd_thumb_active\',\r\n		\'smd_thumb_active_new\',\r\n		\'smd_thumb_crop\',\r\n		\'smd_thumb_sharpen\',\r\n		\'smd_thumb_default\',\r\n		\'smd_thumb_selected\',\r\n		\'smd_thumb_cat_selected\',\r\n		\'smd_thumb_usr_selected\',\r\n		\'smd_thumb_group_type\',\r\n	)));\r\n\r\n	// Sanitize\r\n	$quality = (is_numeric($smd_thumb_quality)) ? (($smd_thumb_quality < 0) ? 75 : (($smd_thumb_quality > 100) ? 75 : $smd_thumb_quality) ) : 75;\r\n	$width = (is_numeric($smd_thumb_width)) ? $smd_thumb_width : 0;\r\n	$height = (is_numeric($smd_thumb_height)) ? $smd_thumb_height : 0;\r\n	$newname = sanitizeForUrl($smd_thumb_newname);\r\n	$msg = \'\';\r\n\r\n	smd_thumb_set_impath();\r\n\r\n	// Table control\r\n	if (gps(\'remove\')) {\r\n		smd_thumb_table_remove();\r\n	} else {\r\n		if (!smd_thumb_table_exist()) {\r\n			smd_thumb_table_install();\r\n		}\r\n	}\r\n\r\n	// Toggle profile panel\r\n	if ($step == \'save_pane_state\') {\r\n		smd_thumbnail_save_pane_state();\r\n		return;\r\n	}\r\n\r\n	if ($step == \'smd_thumb_create_group\') {\r\n		switch ($smd_thumb_group_type) {\r\n			case \'all\':\r\n				smd_thumb_create_group(\'all\');\r\n				break;\r\n			case \'sel\':\r\n				smd_thumb_create_group(\'sel\', rtrim($smd_thumb_selected, \',\'));\r\n				break;\r\n			case \'cat\':\r\n				smd_thumb_create_group(\'cat\', $smd_thumb_cat_selected);\r\n				break;\r\n			case \'usr\':\r\n				smd_thumb_create_group(\'usr\', $smd_thumb_usr_selected);\r\n				break;\r\n		}\r\n		set_pref(\'pane_smd_thumb_group_type\', $smd_thumb_group_type, \'smd_thumb\', PREF_HIDDEN, \'text_input\', 0, PREF_PRIVATE);\r\n		$step = \'smd_thumb_prefs\';\r\n	}\r\n\r\n	// Save profile\r\n	if ($step == \'smd_thumb_profile_save\') {\r\n		if (smd_thumb_table_exist()) {\r\n			if ($smd_thumb_cancel) {\r\n				// Do nothing\r\n			} else if ($smd_thumb_add) {\r\n				// Create new profile\r\n				$exists = safe_row(\'*\', SMD_THUMB, \"name=\'\".doSlash($newname).\"\'\");\r\n				if ($exists) {\r\n					$msg = gTxt(\'smd_thumb_profile_exists\', array(\'{name}\' => doSlash($newname)));\r\n				} else {\r\n					if ($newname) {\r\n						$flags = 0;\r\n						$flags = ($smd_thumb_active_new) ? $flags | SMD_THUMB_ACTIVE : $flags;\r\n						$flags = ($smd_thumb_crop) ? $flags | SMD_THUMB_CROP : $flags;\r\n						$flags = ($smd_thumb_sharpen) ? $flags | SMD_THUMB_SHARP : $flags;\r\n\r\n						safe_insert(SMD_THUMB,\r\n							\"name=\'\".doSlash($newname).\"\'\".\r\n							\", width=\'\".doSlash($width).\"\'\".\r\n							\", height=\'\".doSlash($height).\"\'\".\r\n							\", quality=\'\".doSlash($quality).\"\'\".\r\n							\", flags=\'\".doSlash($flags).\"\'\"\r\n						);\r\n\r\n						// Alter the default thumbnail\r\n						if ($smd_thumb_default) {\r\n							set_pref(\'smd_thumb_default_profile\', $newname, \'smd_thumb\', PREF_HIDDEN);\r\n						}\r\n\r\n						// Create the thumbnail directory\r\n						@mkdir(IMPATH.$newname);\r\n					}\r\n				}\r\n			} else {\r\n				// Update existing profile\r\n				$name = sanitizeForUrl($smd_thumb_name);\r\n				$exists = safe_row(\'*\', SMD_THUMB, \"name=\'\".doSlash($newname).\"\'\");\r\n				if ($exists && strtolower($newname) != strtolower($name)) {\r\n					$msg = gTxt(\'smd_thumb_profile_exists\', array(\'{name}\' => doSlash($newname)));\r\n				} else {\r\n					if ($newname) {\r\n						$flags = 0;\r\n						$flags = ($smd_thumb_active) ? $flags | SMD_THUMB_ACTIVE : $flags;\r\n						$flags = ($smd_thumb_crop) ? $flags | SMD_THUMB_CROP : $flags;\r\n						$flags = ($smd_thumb_sharpen) ? $flags | SMD_THUMB_SHARP : $flags;\r\n\r\n						safe_update(\r\n							SMD_THUMB,\r\n							\"name=\'\".doSlash($newname).\"\'\"\r\n								.\", width=\'\".doSlash($width).\"\'\"\r\n								.\", height=\'\".doSlash($height).\"\'\"\r\n								.\", quality=\'\".doSlash($quality).\"\'\"\r\n								.\", flags=\'\".doSlash($flags).\"\'\"\r\n							, \"name=\'\".doSlash($name).\"\'\"\r\n						);\r\n\r\n						// Alter the default thumbnail\r\n						if ($smd_thumb_default) {\r\n							set_pref(\'smd_thumb_default_profile\', $newname, \'smd_thumb\', PREF_HIDDEN);\r\n						} else {\r\n							// Remove the default flag if this used to be the default\r\n							if (get_pref(\'smd_thumb_default_profile\') == doSlash($name)) {\r\n								set_pref(\'smd_thumb_default_profile\', \'\', \'smd_thumb\', PREF_HIDDEN);\r\n							}\r\n						}\r\n\r\n						// Adjust the thumbnail directory if the name has changed\r\n						if ($newname != $name) {\r\n							rename(IMPATH.$name, IMPATH.$newname);\r\n						}\r\n					}\r\n				}\r\n			}\r\n		} else {\r\n			$msg = gTxt(\'smd_thumb_tables_not_installed\');\r\n		}\r\n		$step = \'smd_thumb_profile\';\r\n	}\r\n\r\n	// Delete profile\r\n	if ($step == \'smd_thumb_profile_delete\') {\r\n		if (smd_thumb_table_exist()) {\r\n			$txp_del = get_pref(\'smd_thumb_txp_delete\');\r\n			$the_dflt = get_pref(\'smd_thumb_default_profile\', \'\', 1);\r\n\r\n			if (!empty($smd_thumb_name)) {\r\n				$name = sanitizeForUrl($smd_thumb_name);\r\n				$ret = safe_delete(SMD_THUMB, \"name=\'\".doSlash($name).\"\'\");\r\n				if ($ret) {\r\n					smd_thumb_rmdir(IMPATH.$name);\r\n\r\n					// Delete all Txp thumbs too?\r\n					if (($name == $the_dflt) && ($txp_del == \'1\')) {\r\n						$rs = safe_rows(\'id, ext\', \'txp_image\', \'thumbnail = 1\');\r\n						foreach ($rs as $row) {\r\n							$path = IMPATH.$row[\'id\'].\'t\'.$row[\'ext\'];\r\n							if (file_exists($path)) {\r\n								unlink($path);\r\n							}\r\n						}\r\n						safe_update(\'txp_image\', \'thumbnail = 0, thumb_w = 0, thumb_h = 0\', \'thumbnail = 1\');\r\n					}\r\n\r\n					// Remove the default flag if this used to be the default\r\n					if (get_pref(\'smd_thumb_default_profile\') == doSlash($name)) {\r\n						set_pref(\'smd_thumb_default_profile\', \'\', \'smd_thumb\', PREF_HIDDEN);\r\n					}\r\n\r\n					$msg = gTxt(\'smd_thumb_profile_deleted\', array(\'{name}\' => doSlash($name)));\r\n				}\r\n			}\r\n		}\r\n		$step = \'smd_thumb_profile\';\r\n	}\r\n\r\n	$qs = array(\r\n		\"event\" => \'image\',\r\n		\"page\" => $page,\r\n		\"sort\" => $sort,\r\n		\"dir\" => $dir,\r\n		\"crit\" => $crit,\r\n		\"search_method\" => $search_method,\r\n	);\r\n\r\n	$privs = safe_field(\'privs\', \'txp_users\', \"name = \'\".doSlash($txp_user).\"\'\");\r\n	$rights = in_array($privs, $smd_thumb_prevs) ? true : false;\r\n\r\n	if ($step == \'smd_thumb_prefs\') {\r\n\r\n		// ** THUMBNAIL PREFS **\r\n\r\n		if (smd_thumb_table_exist()) {\r\n			$pro_dflt = get_pref(\'smd_thumb_default_profile\', \'\', 1);\r\n\r\n			// Subselect lists for type \'cat\' and \'usr\'\r\n			$rs = getTree(\'root\', \'image\');\r\n			$allCats = $rs ? treeSelectInput(\'smd_thumb_cat_selected\', $rs, \'\') : \'\';\r\n			$allCats = \"<span>\".str_replace(array(\"\\n\", \'-\'), array(\'\', \'&#45;\'), str_replace(\'</\', \'<\\/\', addslashes($allCats))).\"<\\/span>\";\r\n			$rs = safe_column(\'name\', \'txp_users\', \"privs not in(0,6) order by name asc\");\r\n			$allUsrs = $rs ? selectInput(\'smd_thumb_usr_selected\', $rs, \'\', true) : \'\';\r\n			$allUsrs = \"<span>\".str_replace(array(\"\\n\", \'-\'), array(\'\', \'&#45;\'), str_replace(\'</\', \'<\\/\', addslashes($allUsrs))).\"<\\/span>\";\r\n\r\n			echo script_js(<<<EOC\r\nfunction smd_thumb_switch_pref(obj, name) {\r\n	if (name == \'create_from\') {\r\n		state = jQuery(obj).val();\r\n	} else {\r\n		state = (jQuery(obj).prop(\'checked\')) ? 1 : 0;\r\n	}\r\n	sendAsyncEvent(\r\n	{\r\n		event: textpattern.event,\r\n		step: \'smd_thumb_switch_pref\',\r\n		smd_thumb_txptype: name,\r\n		smd_thumb_state: state\r\n	});\r\n}\r\nfunction smd_thumb_copy_selected() {\r\n	jQuery(\"#images_form tbody .multi-edit input:checked, #images_form tbody .txp-list-col-multi-edit input:checked\").each(function() {\r\n		jQuery(\"#smd_thumb_selected\").val(jQuery(\"#smd_thumb_selected\").val() + jQuery(this).val() + \',\');\r\n	});\r\n}\r\nfunction smd_thumb_subsel(obj) {\r\n	obj = jQuery(obj);\r\n	dest = jQuery(\'#smd_thumb_subsel\');\r\n	item = obj.val();\r\n	switch (item) {\r\n		case \'all\':\r\n		case \'sel\':\r\n			dest.empty();\r\n			break;\r\n		case \'cat\':\r\n			dest.empty().prepend(\'{$allCats}\');\r\n			break;\r\n		case \'usr\':\r\n			dest.empty().prepend(\'{$allUsrs}\');\r\n			break;\r\n	}\r\n}\r\njQuery(function() {\r\n	jQuery(\'#smd_thumb_group_type\').change();\r\n});\r\nEOC\r\n		);\r\n\r\n			$pnlbtn = \'<a class=\"navlink\" href=\"?event=image\'.a.\'sort=\'.$sort.a.\'dir=\'.$dir.a.\'page=\'.$page.a.\'search_method=\'.$search_method.a.\'crit=\'.$crit.\'\">\'.gTxt(\'smd_thumb_btn_pnl\').\'</a>\';\r\n			$grpbtn = \'<button class=\"navlink\" type=\"submit\" onclick=\"smd_thumb_copy_selected(); return confirm(\\\'\'.gTxt(\'smd_thumb_create_group_confirm\').\'\\\');\">\'.gTxt(\'smd_thumb_all_thumbs\').\'</button>\';\r\n			$grpTypes = array(\r\n				\'sel\' => gTxt(\'smd_thumb_bysel\'),\r\n				\'cat\' => gTxt(\'smd_thumb_bycat\'),\r\n				\'usr\' => gTxt(\'smd_thumb_byusr\'),\r\n				\'all\' => gTxt(\'smd_thumb_byall\'),\r\n			);\r\n\r\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>\';\r\n\r\n			$out[] = \'<div id=\"smd_thumb_profiles\"><div 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></div><div id=\"smd_thumbnail_profiles\" class=\"toggle\" style=\"display:\'.(get_pref(\'pane_smd_thumbnail_profiles_visible\') ? \'block\' : \'none\').\'\">\';\r\n			$out[] = \'<div class=\"txp-buttons\">\'.$pnlbtn.\'</div>\';\r\n\r\n			$out[] = \'<div><div>\'.gTxt(\'smd_thumb_batch_legend\').\'</div>\';\r\n			$out[] = \'<div id=\"smd_thumb_batch\"><span id=\"smd_thumb_bcurr\"></span><span id=\"smd_thumb_btot\"></span></div>\';\r\n			$out[] = \'<form method=\"post\" name=\"smd_thumb_multi_edit\" id=\"smd_thumb_multi_edit\" action=\"\'.join_qs($qs).\'\">\';\r\n			$out[] = startTable();\r\n			$out[] = tr(td(gTxt(\'smd_thumb_batch_preamble\').$grpbtn.$grpOpts));\r\n			$out[] = endTable();\r\n			$out[] = fInput(\'hidden\', \'smd_thumb_selected\', \'\', \'\', \'\', \'\', \'\', \'\', \'smd_thumb_selected\');\r\n			$out[] = eInput($smd_thumb_event);\r\n			$out[] = sInput(\'smd_thumb_create_group\');\r\n			$out[] = \'</form>\';\r\n			$out[] = \'</div>\';\r\n\r\n			if ($rights) {\r\n				$out[] = \'<div><div>\'.gTxt(\'smd_thumb_prefs\').\'</div>\';\r\n				$out[] = \'<form method=\"post\" name=\"smd_thumb_prefs\" id=\"smd_thumb_prefs_form\" action=\"\'.join_qs($qs).\'\">\';\r\n				$out[] = startTable();\r\n\r\n				$txp_rf = get_pref(\'smd_thumb_create_from\', \'full\');\r\n				$out[] = tr(\r\n					tda(gTxt(\'smd_thumb_txp_create_from\'), \' class=\"pref-label\"\') .\r\n					tda(\r\n						\'<input type=\"radio\" name=\"smd_thumb_create_from\" value=\"full\"\'.(($txp_rf==\'full\') ? \' checked=\"checked\"\' : \'\'). \' class=\"radio\" onchange=\"smd_thumb_switch_pref(this, \\\'create_from\\\');\" />\'.gTxt(\'smd_thumb_txp_create_from_full\')\r\n						.\'<input type=\"radio\" name=\"smd_thumb_create_from\" value=\"thumb\"\'.(($txp_rf==\'thumb\') ? \' checked=\"checked\"\' : \'\'). \' class=\"radio\" onchange=\"smd_thumb_switch_pref(this, \\\'create_from\\\');\" />\'.gTxt(\'smd_thumb_txp_create_from_thumb\')\r\n					));\r\n\r\n				if ($pro_dflt) {\r\n					$txp_c = get_pref(\'smd_thumb_txp_create\');\r\n					$txp_d = get_pref(\'smd_thumb_txp_delete\');\r\n					$out[] = tr(\r\n						tda(gTxt(\'smd_thumb_txp_default_sync\'), \' class=\"pref-label\"\') .\r\n						tda(\r\n							\'<input type=\"checkbox\" name=\"smd_thumb_txp_create\" value=\"1\"\'.(($txp_c) ? \' checked=\"checked\"\' : \'\'). \' class=\"checkbox\" onchange=\"smd_thumb_switch_pref(this, \\\'txp_create\\\');\" />\'.\r\n							gTxt(\'smd_thumb_create\').\r\n							\'<input type=\"checkbox\" name=\"smd_thumb_txp_delete\" value=\"1\"\'.(($txp_d) ? \' checked=\"checked\"\' : \'\'). \' class=\"checkbox\" onchange=\"smd_thumb_switch_pref(this, \\\'txp_delete\\\');\" />\'.\r\n							gTxt(\'smd_thumb_delete\')\r\n						));\r\n				}\r\n\r\n				$txp_ar = get_pref(\'smd_thumb_auto_replace\');\r\n				$out[] = tr(\r\n					tda(gTxt(\'smd_thumb_txp_auto_replace\'), \' class=\"pref-label noline\"\') .\r\n					tda(\r\n						\'<input type=\"checkbox\" name=\"smd_thumb_auto_replace\" value=\"1\"\'.(($txp_ar) ? \' checked=\"checked\"\' : \'\'). \' class=\"checkbox\" onchange=\"smd_thumb_switch_pref(this, \\\'auto_replace\\\');\" />\'\r\n					, \' class=\"noline\"\'));\r\n\r\n				$out[] = endTable();\r\n				$out[] = \'</form></div>\';\r\n			}\r\n			$out[] = \'</div></div>\';\r\n		}\r\n	} else {\r\n\r\n		// ** THUMBNAIL PROFILES **\r\n\r\n		$editFocus = ($step == \'smd_thumb_profile_edit\') ? \'jQuery(\"#smd_thumb_profile_edited input[name=\\\'smd_thumb_newname\\\']\").focus();\' : \'\';\r\n\r\n		echo script_js(<<<EOC\r\nfunction smd_thumb_togglenew() {\r\n	box = jQuery(\"#smd_thumb_profile_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\nfunction smd_thumb_switch_active(name) {\r\n	sendAsyncEvent(\r\n	{\r\n		event: textpattern.event,\r\n		step: \'smd_thumb_switch_active\',\r\n		smd_thumb_profile: name\r\n	}, smd_thumb_switch_active_done);\r\n}\r\nfunction smd_thumb_switch_active_done(data) {\r\n	var smd_thumb_profile = jQuery(\'#smd_thumb_profile_\'+jQuery(data).find(\'smd_thumb_profile\').attr(\'value\'));\r\n	smd_thumb_profile.toggleClass(\'smd_inactive\');\r\n	if (smd_thumb_profile.hasClass(\'smd_inactive\')) {\r\n		smd_thumb_profile.find(\'input:checkbox\').removeAttr(\'checked\');\r\n	} else {\r\n		smd_thumb_profile.find(\'input:checkbox\').attr(\'checked\', \'checked\');\r\n	}\r\n}\r\n\r\njQuery(function() {\r\n	jQuery(\'.smd_thumb_heading_active\').click(function() {\r\n		jQuery(\'.smd_thumb_row input:checkbox\').each(function() {\r\n			smd_thumb_switch_active(jQuery(this).parent().attr(\'class\'));\r\n		});\r\n	});\r\n	{$editFocus}\r\n});\r\nEOC\r\n		);\r\n\r\n		$newbtn = \'<a class=\"navlink\" href=\"#\" onclick=\"return smd_thumb_togglenew();\">\'.gTxt(\'smd_thumb_new\').\'</a>\';\r\n		$prefbtn = \'<a class=\"navlink\" 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.\'\">\'.(($rights) ? gTxt(\'smd_thumb_btn_tools_prefs\') : gTxt(\'smd_thumb_btn_tools\')).\'</a>\';\r\n\r\n		$headings = n.\'<thead>\'.tr(\r\n			n.column_head(\'name\', \'name\', \'image\', false, \'asc\').\r\n			column_head(\'thumb_width\', \'width\', \'image\', false).\r\n			column_head(\'thumb_height\', \'height\', \'image\', false).\r\n			column_head(gTxt(\'smd_thumb_quality\'), \'quality\', \'image\', false).\r\n			column_head(\'keep_square_pixels\', \'crop\', \'image\', false).\r\n			column_head(gTxt(\'smd_thumb_sharpen\'), \'sharpen\', \'image\', false).\r\n			column_head(\'active\', \'active\', \'image\', false, \'\', \'\', \'\', \'smd_thumb_heading_active\').\r\n			column_head(\'default\', \'default\', \'image\', false).\r\n			column_head(gTxt(\'smd_thumb_actions\'), \'actions\', \'image\', false)\r\n		).\'</thead>\';\r\n\r\n		$out[] = \'<div id=\"smd_thumb_profiles\"><div class=\"txp-summary lever\'.(get_pref(\'pane_smd_thumbnail_profiles_visible\') ? \' expanded\' : \'\').\'\"><a href=\"#smd_thumbnail_profiles\">\'.gTxt(\'smd_thumb_profile_heading\').\'</a></div><div id=\"smd_thumbnail_profiles\" class=\"toggle\" style=\"display:\'.(get_pref(\'pane_smd_thumbnail_profiles_visible\') ? \'block\' : \'none\').\'\">\';\r\n		$out[] = \'<div class=\"txp-buttons\">\'.$newbtn.n.$prefbtn.\'</div>\';\r\n\r\n		// Main list of profiles\r\n		$out[] = \'<form method=\"post\" name=\"smd_thumb_profile_form\" id=\"smd_thumb_profile_form\" action=\"\'.join_qs($qs).\'\">\';\r\n		$out[] = startTable();\r\n		$out[] = $headings;\r\n\r\n		if (smd_thumb_table_exist()) {\r\n			$rs = safe_rows(\'*\', SMD_THUMB, \'1=1 ORDER BY name\');\r\n			$pro_dflt = get_pref(\'smd_thumb_default_profile\', \'\', 1);\r\n\r\n			// New Profile row\r\n			$out[]= \'<tr id=\"smd_thumb_profile_create\" class=\"smd_hidden\">\';\r\n			$out[] = td(sInput(\'smd_thumb_profile_save\').fInput(\'text\', \'smd_thumb_newname\', (($step==\'smd_thumb_profile_save\') ? $smd_thumb_name : \'\'), \'smd_focus\'))\r\n				.td(fInput(\'text\', \'smd_thumb_width\', (($step==\'smd_thumb_profile_save\') ? $width : \'\'), \'\', \'\', \'\', \'4\'))\r\n				.td(fInput(\'text\', \'smd_thumb_height\', (($step==\'smd_thumb_profile_save\') ? $height : \'\'), \'\', \'\', \'\', \'4\'))\r\n				.td(fInput(\'text\', \'smd_thumb_quality\', (($step==\'smd_thumb_profile_save\') ? $quality : \'\'), \'\', \'\', \'\', \'4\'))\r\n				.td(checkbox(\'smd_thumb_crop\', \'1\', (($step==\'smd_thumb_profile_save\') ? $smd_thumb_crop : 0)))\r\n				.td(checkbox(\'smd_thumb_sharpen\', \'1\', (($step==\'smd_thumb_profile_save\') ? $smd_thumb_sharpen : 0)))\r\n				.td(checkbox(\'smd_thumb_active_new\', \'1\', 1))\r\n				.td(checkbox(\'smd_thumb_default\', \'1\', 0))\r\n				.td(fInput(\'submit\', \'smd_thumb_add\', gTxt(\'add\')));\r\n			$out[]= \'</tr>\';\r\n\r\n			// Remaining rows\r\n			foreach ($rs as $row) {\r\n				$link_edt = join_qs($qs).a.\'step=smd_thumb_profile_edit\'.a.\'smd_thumb_name=\'.$row[\'name\'];\r\n				$link_del = join_qs($qs).a.\'step=smd_thumb_profile_delete\'.a.\'smd_thumb_name=\'.$row[\'name\'];\r\n				$btnEdt = \'<a href=\"\'.$link_edt.\'\">[\' . gTxt(\'edit\') . \']</a>\';\r\n				$btnDel = \'<a href=\"\'.$link_del.\'\" onclick=\"return confirm(\\\'\'.gTxt(\'smd_thumb_delete_confirm\', array(\"{name}\" => $row[\'name\'])).\'\\\');\">[\' . gTxt(\'delete\') . \']</a>\';\r\n				$active = ($row[\'flags\'] & SMD_THUMB_ACTIVE) ? 1 : 0;\r\n				$crop = ($row[\'flags\'] & SMD_THUMB_CROP) ? 1 : 0;\r\n				$sharpen = ($row[\'flags\'] & SMD_THUMB_SHARP) ? 1 : 0;\r\n\r\n				if ($step == \'smd_thumb_profile_edit\' && $row[\'name\'] == $smd_thumb_name) {\r\n					$btnSave = fInput(\'submit\', \'smd_thumb_save\', gTxt(\'Save\'));\r\n					$btnCancel = fInput(\'submit\', \'smd_thumb_cancel\', gTxt(\'Cancel\'));\r\n					$out[] = tr(\r\n						td(hInput(\'smd_thumb_name\', $row[\'name\']).fInput(\'text\', \'smd_thumb_newname\', $row[\'name\']))\r\n						.td(fInput(\'text\', \'smd_thumb_width\', $row[\'width\'], \'\', \'\', \'\', \'4\'))\r\n						.td(fInput(\'text\', \'smd_thumb_height\', $row[\'height\'], \'\', \'\', \'\', \'4\'))\r\n						.td(fInput(\'text\', \'smd_thumb_quality\', $row[\'quality\'], \'\', \'\', \'\', \'4\'))\r\n						.td(checkbox(\'smd_thumb_crop\', \'1\', $crop))\r\n						.td(checkbox(\'smd_thumb_sharpen\', \'1\', $sharpen))\r\n						.td(checkbox(\'smd_thumb_active\', \'1\', $active), \'\', $row[\'name\'])\r\n						.td(checkbox(\'smd_thumb_default\', \'1\', (($row[\'name\'] == $pro_dflt) ? 1 : 0)))\r\n						.td($btnSave.$btnCancel)\r\n					, \' id=\"smd_thumb_profile_edited\"\');\r\n				} else {\r\n					$out[] = tr(\r\n						td(href($row[\'name\'], $link_edt)).\r\n						td($row[\'width\']).\r\n						td($row[\'height\']).\r\n						td($row[\'quality\']).\r\n						td(($crop) ? gTxt(\'yes\') : gTxt(\'no\')).\r\n						td(($sharpen) ? gTxt(\'yes\') : gTxt(\'no\')).\r\n						td(\'<input type=\"checkbox\" name=\"smd_thumb_active_\'.$row[\'name\'].\'\" value=\"1\"\'.(($active) ? \' checked=\"checked\"\' : \'\'). \' class=\"checkbox\" onchange=\"smd_thumb_switch_active(\\\'\'.$row[\'name\'].\'\\\');\" />\', \'\', $row[\'name\']).\r\n						td(($row[\'name\'] == $pro_dflt) ? gTxt(\'yes\') : gTxt(\'no\')).\r\n						td($btnDel)\r\n					, \' id=\"smd_thumb_profile_\'.$row[\'name\'].\'\" class=\"smd_thumb_row\' . (($active) ? \'\' : \' smd_inactive\').\'\"\');\r\n				}\r\n			}\r\n		}\r\n\r\n		$out[] = endTable();\r\n		$out[] = \'</form></div></div>\';\r\n	}\r\n\r\n	return join(\'\', $out);\r\n}\r\n\r\n// ------------------------\r\n// Useful function for other plugins to use to retrieve a list of current profiles\r\nfunction smd_thumb_get_profiles($active=1, $order=\'name\') {\r\n	$allowedOrders = array(\'name\', \'width\', \'height\',\'quality\');\r\n	$order = in_array($order, $allowedOrders) ? $order : \'name\';\r\n\r\n	$where[] = \'1=1\';\r\n	if ($active) {\r\n		$where[] = \'flags & \'.SMD_THUMB_ACTIVE;\r\n	}\r\n\r\n	$rs = safe_rows(\'*\', SMD_THUMB, join(\' AND \', $where) . \' ORDER BY \'.$order);\r\n\r\n	return $rs;\r\n}\r\n\r\n// ------------------------\r\n// Delete a thumbnail directory and its contents\r\nfunction smd_thumb_rmdir($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 (filetype($dir.DS.$object) == \"dir\") smd_thumb_rmdir($dir.DS.$object); else unlink($dir.DS.$object);\r\n			}\r\n		}\r\n		reset($objects);\r\n		rmdir($dir);\r\n	}\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_set_impath() {\r\n	global $path_to_site, $img_dir;\r\n	if (!defined(\'IMPATH\')) {\r\n		define(\'IMPATH\', $path_to_site.DS.$img_dir.DS);\r\n	}\r\n}\r\n\r\n// ------------------------\r\n// Add thumbnail table if not already installed\r\n// Flags: Bit 0=Active / 1=Crop / 2 = Sharpen\r\nfunction smd_thumb_table_install() {\r\n	$GLOBALS[\'txp_err_count\'] = 0;\r\n	$ret = \'\';\r\n	$sql = array();\r\n	$sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.SMD_THUMB.\"` (\r\n		`name` varchar(32) NOT NULL default \'\',\r\n		`width` int(4) NOT NULL default \'0\',\r\n		`height` int(4) NOT NULL default \'0\',\r\n		`quality` tinyint(2) NULL default \'75\',\r\n		`flags` tinyint(2) NOT NULL default \'0\',\r\n		PRIMARY KEY (`name`)\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	// Backup the current thumb prefs\r\n	$exists = safe_row(\'*\', \'txp_prefs\', \"name=\'smd_thumb_backup_w\'\");\r\n	if (!$exists) {\r\n		set_pref(\'smd_thumb_backup_w\', get_pref(\'thumb_w\'), \'smd_thumb\', PREF_HIDDEN, \'text_input\', 0);\r\n		set_pref(\'smd_thumb_backup_h\', get_pref(\'thumb_h\'), \'smd_thumb\', PREF_HIDDEN, \'text_input\', 1);\r\n		set_pref(\'smd_thumb_backup_c\', get_pref(\'thumb_crop\'), \'smd_thumb\', PREF_HIDDEN, \'text_input\', 2);\r\n\r\n		// Erase the current thumb prefs\r\n		set_pref(\'thumb_w\', \'\', \'image\', PREF_HIDDEN);\r\n		set_pref(\'thumb_h\', \'\', \'image\', PREF_HIDDEN);\r\n		set_pref(\'thumb_crop\', \'\', \'image\', PREF_HIDDEN);\r\n	}\r\n}\r\n\r\n// ------------------------\r\n// Drop table if in database\r\nfunction smd_thumb_table_remove() {\r\n	$ret = \'\';\r\n	$sql = array();\r\n	$GLOBALS[\'txp_err_count\'] = 0;\r\n	if (smd_thumb_table_exist()) {\r\n		$sql[] = \"DROP TABLE IF EXISTS \" .PFX.SMD_THUMB. \"; \";\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	// Restore the original thumb prefs\r\n	$exists = safe_row(\'*\', \'txp_prefs\', \"name=\'smd_thumb_backup_w\'\");\r\n	if ($exists) {\r\n		set_pref(\'thumb_w\', get_pref(\'smd_thumb_backup_w\'), \'image\', PREF_HIDDEN);\r\n		set_pref(\'thumb_h\', get_pref(\'smd_thumb_backup_h\'), \'image\', PREF_HIDDEN);\r\n		set_pref(\'thumb_crop\', get_pref(\'smd_thumb_backup_c\'), \'image\', PREF_HIDDEN);\r\n\r\n	}\r\n	// Erase the backup thumb prefs\r\n	safe_delete(\'txp_prefs\', \"name IN(\'smd_thumb_backup_w\', \'smd_thumb_backup_h\', \'smd_thumb_backup_c\', \'smd_thumb_default_profile\', \'pane_smd_thumbnail_profiles_visible\', \'pane_smd_thumbnails_visible\')\");\r\n}\r\n\r\n// ------------------------\r\nfunction smd_thumb_table_exist($all=\'\') {\r\n	if ($all) {\r\n		$tbls = array(SMD_THUMB => 5);\r\n		$out = count($tbls);\r\n		foreach ($tbls as $tbl => $cols) {\r\n			if (gps(\'debug\')) {\r\n				echo \"++ TABLE \".$tbl.\" HAS \".count(@safe_show(\'columns\', $tbl)).\" COLUMNS; REQUIRES \".$cols.\" ++\".br;\r\n			}\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		if (gps(\'debug\')) {\r\n			echo \"++ TABLE \".SMD_THUMB.\" HAS \".count(@safe_show(\'columns\', SMD_THUMB)).\" COLUMNS;\";\r\n		}\r\n		return(@safe_show(\'columns\', SMD_THUMB));\r\n	}\r\n}\r\n\r\n// -------------------------------------------------------------\r\nfunction smd_thumbnail_save_pane_state() {\r\n	$panes = array(\'smd_thumbnail_profiles\', \'smd_thumbnails\');\r\n	$pane = gps(\'pane\');\r\n	if (in_array($pane, $panes))\r\n	{\r\n		set_pref(\"pane_{$pane}_visible\", (gps(\'visible\') == \'true\' ? \'1\' : \'0\'), \'smd_thumb\', PREF_HIDDEN, \'yesnoradio\', 0, PREF_PRIVATE);\r\n		send_xml_response();\r\n	} else {\r\n		send_xml_response(array(\'http-status\' => \'400 Bad Request\'));\r\n	}\r\n}\r\n\r\n//*****************\r\n// PUBLIC SIDE TAGS\r\n//*****************\r\n// ------------------------\r\nfunction smd_thumbnail($atts, $thing=NULL) {\r\n	global $thisimage, $img_dir;\r\n\r\n	extract(lAtts(array(\r\n		\'type\'       => get_pref(\'smd_thumb_default_profile\', \'\'),\r\n		\'id\'         => \'\',\r\n		\'name\'       => \'\',\r\n		\'escape\'     => \'html\',\r\n		\'wraptag\'    => \'\',\r\n		\'class\'      => \'\',\r\n		\'html_id\'    => \'\',\r\n		\'style\'      => \'\',\r\n		\'link\'       => \'\',\r\n		\'link_rel\'   => \'\',\r\n		\'poplink\'    => 0,\r\n		\'add_stamp\'  => 0,\r\n		\'width\'      => \'\',\r\n		\'height\'     => \'\',\r\n		\'force_size\' => \'\',\r\n		\'display\'    => \'thumbnail\', // thumbnail (full img tag) or url\r\n		\'form\'       => \'\',\r\n	),$atts));\r\n\r\n	$thing = (empty($form)) ? $thing : fetch_form($form);\r\n\r\n	if ($name) {\r\n		$name = doSlash($name);\r\n		$rs = safe_row(\'*\', \'txp_image\', \"name = \'$name\' limit 1\");\r\n	} elseif ($id) {\r\n		$id = (int) $id;\r\n		$rs = safe_row(\'*\', \'txp_image\', \"id = $id limit 1\");\r\n	} elseif ($thisimage) {\r\n		$id = (int) $thisimage[\'id\'];\r\n		$rs = $thisimage;\r\n	} else {\r\n		trigger_error(gTxt(\'unknown_image\'));\r\n		return;\r\n	}\r\n\r\n	smd_thumb_set_impath();\r\n\r\n	if ($rs) {\r\n		extract($rs);\r\n		$thumbinfo = safe_row(\'*\', SMD_THUMB, \"name=\'\".doSlash($type).\"\'\");\r\n		if ($thumbinfo) {\r\n			$dir = sanitizeForUrl($thumbinfo[\'name\']);\r\n			$path = IMPATH.$dir.DS.$id.$ext;\r\n			if (file_exists($path)) {\r\n				// Drop out if all we need to display is the thumb\'s URL\r\n				if ($display == \'url\') {\r\n					return ihu.$img_dir.\'/\'.$dir.\'/\'.$id.$ext;\r\n				}\r\n				if ($escape == \'html\') {\r\n					$alt = htmlspecialchars($alt);\r\n					$caption = htmlspecialchars($caption);\r\n				}\r\n\r\n				$meta[\'url\'] = ihu.$img_dir.\'/\'.$dir.\'/\'.$id.$ext;\r\n				$meta[\'alt\'] = $alt;\r\n				$meta[\'type\'] = $type;\r\n				$meta[\'width\'] = $width;\r\n				$meta[\'height\'] = $height;\r\n				$force_size = do_list($force_size);\r\n				if (in_array(\'width\', $force_size)) {\r\n					$meta[\'forcew\'] = 1;\r\n				}\r\n				if (in_array(\'height\', $force_size)) {\r\n					$meta[\'forceh\'] = 1;\r\n				}\r\n				if ($add_stamp == 0) { $meta[\'stamp\'] = 1; } // Negative logic since we want the stamp on by default for the admin side\r\n				if ($name) { $meta[\'name\'] = $name; }\r\n				if ($caption) { $meta[\'title\'] = $caption; }\r\n// $meta[\'title\'] = gTxt(\'smd_thumb_image\') . $rs[\'w\'] . \" &#215; \" . $rs[\'h\'] . sp .  gTxt(\'smd_thumb_profile\') . $thumbinfo[\'width\'] . \" &#215; \" . $thumbinfo[\'height\'];\r\n\r\n				if ($category) {\r\n					$meta[\'category\'] = $category;\r\n					$meta[\'category_title\'] = fetch_category_title($category, \'image\');\r\n				}\r\n				if ($author) { $meta[\'author\'] = $author; }\r\n				if ($date) { $meta[\'date\'] = $date; }\r\n				if ($html_id && !$wraptag) { $meta[\'id\'] = $html_id; }\r\n				if ($class && !$wraptag) { $meta[\'class\'] = $class; } else { $class=$type; }\r\n				if ($style) { $meta[\'style\'] = $style; }\r\n\r\n				$out = smd_thumb_img($thumbinfo, $rs, $meta, $thing);\r\n\r\n				if ($link) {\r\n					$out = href($out, imagesrcurl($id, $ext), (!empty($link_rel) ? \' rel=\"\'.$link_rel.\'\"\' : \'\').\' title=\"\'.$caption.\'\"\');\r\n				} elseif ($poplink) {\r\n					$out = \'<a href=\"\'.imagesrcurl($id, $ext).\'\"\'.\r\n						\' onclick=\"window.open(this.href, \\\'popupwindow\\\', \'.\r\n						\'\\\'width=\'.$w.\', height=\'.$h.\', scrollbars, resizable\\\'); return false;\">\'.$out.\'</a>\';\r\n				}\r\n\r\n				return ($wraptag) ? doTag($out, $wraptag, $class, \'\', $html_id) : $out;\r\n			}\r\n		}\r\n	}\r\n\r\n	trigger_error(gTxt(\'unknown_image\'));\r\n}\r\n\r\n// ------------------------\r\nfunction smd_if_thumbnail($atts, $thing) {\r\n	global $thisimage;\r\n	assert_image();\r\n\r\n	smd_thumb_set_impath();\r\n\r\n	extract(lAtts(array(\r\n		\'type\' => @get_pref(\'smd_thumb_default_profile\'),\r\n	),$atts));\r\n\r\n	$thumbinfo = safe_row(\'*\', SMD_THUMB, \"name=\'\".doSlash($type).\"\'\");\r\n	$ret = false;\r\n	if ($thumbinfo) {\r\n		$path = IMPATH.sanitizeForUrl($thumbinfo[\'name\']).DS.$thisimage[\'id\'].$thisimage[\'ext\'];\r\n		$ret = file_exists($path);\r\n	}\r\n	return parse(EvalElse($thing, $ret));\r\n}\r\n\r\n// Display data for form/container usage\r\nfunction smd_thumbnail_info($atts, $thing=NULL) {\r\n	global $smd_thumb_data;\r\n\r\n	extract(lAtts(array(\r\n		\'item\'    => \'\',\r\n		\'wraptag\' => \'\',\r\n		\'break\'   => \'\',\r\n		\'class\'   => \'\',\r\n		\'debug\'   => 0,\r\n	), $atts));\r\n\r\n	$tdata = is_array($smd_thumb_data) ? $smd_thumb_data : array();\r\n\r\n	if ($debug) {\r\n		echo \'++ AVAILABLE INFO ++\';\r\n		dmp($tdata);\r\n	}\r\n\r\n	$items = do_list($item);\r\n	$out = array();\r\n	foreach ($items as $it) {\r\n		if (isset($tdata[$it])) {\r\n			$out[] = $tdata[$it];\r\n		}\r\n	}\r\n\r\n	return doWrap($out, $wraptag, $break, $class);\r\n}\r\n','6ecacf37a608e39154aa654371f9f6e5',3,5,2);

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

CREATE TABLE `txp_prefs` (
  `prefs_id` int(11) NOT NULL DEFAULT '1',
  `name` varchar(255) NOT NULL DEFAULT '',
  `val` text NOT NULL,
  `type` smallint(5) unsigned NOT NULL DEFAULT '2',
  `event` varchar(12) NOT NULL DEFAULT 'publish',
  `html` varchar(64) NOT NULL DEFAULT 'text_input',
  `position` smallint(5) unsigned NOT NULL DEFAULT '0',
  `user_name` varchar(64) NOT NULL DEFAULT '',
  UNIQUE KEY `prefs_idx` (`prefs_id`,`name`,`user_name`),
  KEY `name` (`name`),
  KEY `user_name` (`user_name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

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

INSERT INTO `txp_prefs` VALUES (1,'prefs_id','1',2,'publish','text_input',0,''),(1,'sitename','Sampology',0,'publish','text_input',20,''),(1,'siteurl','sampology.com',0,'publish','text_input',40,''),(1,'site_slogan','',0,'publish','text_input',30,''),(1,'language','en-gb',2,'publish','languages',40,''),(1,'url_mode','1',2,'publish','text_input',0,''),(1,'timeoffset','0',2,'publish','text_input',0,''),(1,'comments_on_default','1',0,'comments','yesnoradio',20,''),(1,'comments_default_invite','Comment',0,'comments','text_input',40,''),(1,'comments_mode','0',0,'comments','commentmode',120,''),(1,'comments_disabled_after','42',0,'comments','weeks',80,''),(1,'use_textile','1',0,'publish','pref_text',200,''),(1,'ping_weblogsdotcom','0',1,'publish','yesnoradio',160,''),(1,'rss_how_many','5',1,'feeds','text_input',40,''),(1,'logging','all',0,'publish','logging',220,''),(1,'use_comments','1',0,'publish','yesnoradio',240,''),(1,'use_categories','1',2,'publish','text_input',0,''),(1,'use_sections','1',2,'publish','text_input',0,''),(1,'send_lastmod','1',1,'publish','yesnoradio',120,''),(1,'path_from_root','/',2,'publish','text_input',0,''),(1,'lastmod','2018-01-10 08:40:08',2,'publish','text_input',0,''),(1,'comments_dateformat','%A %B %Oe, %Y',0,'comments','dateformats',140,''),(1,'dateformat','since',0,'publish','dateformats',140,''),(1,'archive_dateformat','%b %Oe, %I:%M %p',0,'publish','dateformats',160,''),(1,'comments_moderate','1',0,'comments','yesnoradio',60,''),(1,'img_dir','images',1,'admin','text_input',20,''),(1,'comments_disallow_images','0',1,'comments','yesnoradio',120,''),(1,'comments_sendmail','0',0,'comments','commentsendmail',180,''),(1,'file_max_upload_size','2000000',1,'admin','text_input',60,''),(1,'path_to_site','/home/youngstr/public_html/sampology.com',2,'publish','text_input',0,''),(1,'timezone_key','Australia/Brisbane',2,'publish','textinput',0,''),(1,'default_event','article',1,'admin','default_event',150,''),(1,'auto_dst','0',0,'publish','yesnoradio',115,''),(1,'permlink_mode','section_title',0,'publish','permlinkmodes',180,''),(1,'comments_are_ol','1',0,'comments','yesnoradio',160,''),(1,'is_dst','0',0,'publish','is_dst',120,''),(1,'locale','en_GB.UTF-8',2,'publish','text_input',0,''),(1,'tempdir','/home/youngstr/public_html/sampology.com/textpattern/tmp',1,'admin','text_input',80,''),(1,'file_base_path','/home/youngstr/public_html/sampology.com/files',1,'admin','text_input',40,''),(1,'blog_uid','e96aa86dd261b84b61e98f9ba0001202',2,'publish','text_input',0,''),(1,'blog_mail_uid','cal@oxygenkiosk.com',2,'publish','text_input',0,''),(1,'blog_time_uid','2005',2,'publish','text_input',0,''),(1,'pane_form_misc_visible','1',2,'form','yesnoradio',0,'calwilson'),(1,'allow_page_php_scripting','1',1,'publish','yesnoradio',300,''),(1,'allow_article_php_scripting','1',1,'publish','yesnoradio',320,''),(1,'default_section','blog',2,'section','text_input',0,''),(1,'comments_use_fat_textile','0',1,'comments','yesnoradio',130,''),(1,'show_article_category_count','1',2,'category','yesnoradio',0,''),(1,'show_comment_count_in_feed','1',1,'feeds','yesnoradio',60,''),(1,'syndicate_body_or_excerpt','1',1,'feeds','yesnoradio',20,''),(1,'include_email_atom','1',1,'feeds','yesnoradio',80,''),(1,'comment_means_site_updated','1',1,'comments','yesnoradio',160,''),(1,'never_display_email','0',1,'comments','yesnoradio',60,''),(1,'comments_require_name','1',1,'comments','yesnoradio',20,''),(1,'comments_require_email','1',1,'comments','yesnoradio',40,''),(1,'articles_use_excerpts','1',1,'publish','yesnoradio',40,''),(1,'allow_form_override','1',1,'publish','yesnoradio',60,''),(1,'attach_titles_to_permalinks','1',1,'publish','yesnoradio',80,''),(1,'permalink_title_format','1',1,'publish','yesnoradio',100,''),(1,'expire_logs_after','7',1,'publish','text_input',200,''),(1,'use_plugins','1',1,'publish','yesnoradio',260,''),(1,'custom_1_set','MakeSticky',1,'custom','checkbox',1,''),(1,'custom_2_set','StickyText',1,'custom','text_input',2,''),(1,'custom_3_set','Vimeo ID',1,'custom','text_input',3,''),(1,'custom_4_set','youtube-id',1,'custom','text_input',4,''),(1,'custom_5_set','makefeatured',1,'custom','checkbox',5,''),(1,'custom_6_set','SoundcloudCode',1,'custom','textarea',6,''),(1,'custom_7_set','Tracklisting',1,'custom','textarea',7,''),(1,'custom_8_set','Release Date',1,'custom','date-picker',8,''),(1,'custom_9_set','gallery-category',1,'custom','text_input',9,''),(1,'custom_10_set','',1,'custom','text_input',10,''),(1,'ping_textpattern_com','1',1,'publish','yesnoradio',180,''),(1,'use_dns','1',1,'publish','yesnoradio',220,''),(1,'admin_side_plugins','1',1,'publish','yesnoradio',280,''),(1,'comment_nofollow','1',1,'comments','yesnoradio',80,''),(1,'use_mail_on_feeds_id','0',1,'feeds','yesnoradio',100,''),(1,'max_url_len','200',1,'publish','text_input',240,''),(1,'spam_blacklists','sbl.spamhaus.org',1,'comments','text_input',140,''),(1,'override_emailcharset','0',1,'admin','yesnoradio',120,''),(1,'production_status','live',0,'publish','prod_levels',80,''),(1,'comments_auto_append','1',0,'comments','yesnoradio',100,''),(1,'dbupdatetime','1489902054',2,'publish','text_input',0,''),(1,'version','4.5.7',2,'publish','text_input',0,''),(1,'gmtoffset','36000',0,'publish','gmtoffset_select',100,''),(1,'plugin_cache_dir','',1,'admin','text_input',100,''),(1,'textile_updated','1',2,'publish','text_input',0,''),(1,'title_no_widow','1',1,'publish','yesnoradio',20,''),(1,'lastmod_keepalive','0',1,'publish','yesnoradio',140,''),(1,'enable_xmlrpc_server','0',1,'admin','yesnoradio',130,''),(1,'smtp_from','',1,'admin','text_input',110,''),(1,'theme_name','classic',1,'admin','themename',160,''),(1,'publish_expired_articles','0',1,'publish','yesnoradio',130,''),(1,'searchable_article_fields','Title, Body',2,'publish','text_input',0,''),(1,'article_sort_column','posted',2,'list','',0,'calwilson'),(1,'article_sort_dir','desc',2,'list','',0,'calwilson'),(1,'image_sort_column','id',2,'image','',0,'calwilson'),(1,'image_sort_dir','desc',2,'image','',0,'calwilson'),(1,'link_sort_column','name',2,'link','',0,'calwilson'),(1,'link_sort_dir','asc',2,'link','',0,'calwilson'),(1,'admin_sort_column','name',2,'admin','',0,'calwilson'),(1,'admin_sort_dir','asc',2,'admin','',0,'calwilson'),(1,'max_custom_fields','10',2,'publish','text_input',0,''),(1,'pane_article_textile_help_visible','1',2,'article','yesnoradio',0,'sam'),(1,'article_sort_column','posted',2,'list','',0,'sam'),(1,'article_sort_dir','desc',2,'list','',0,'sam'),(1,'pane_article_more_visible','1',2,'article','yesnoradio',0,'sam'),(1,'lam_image_uploader_show_thumbnail','1',2,'admin','yesnoradio',0,''),(1,'lam_image_uploader_show_image_id','1',2,'admin','yesnoradio',0,''),(1,'lam_image_uploader_image_caption','1',2,'admin','yesnoradio',0,''),(1,'lam_image_uploader_show_attach_image','1',2,'admin','yesnoradio',0,''),(1,'lam_image_uploader_show_append_image','1',2,'admin','yesnoradio',0,''),(1,'lam_image_uploader_auto_append','1',2,'admin','yesnoradio',0,''),(1,'lam_image_uploader_image_categories','1',2,'admin','yesnoradio',0,''),(1,'lam_image_uploader_move_sidebar','0',2,'admin','yesnoradio',0,''),(1,'pane_article_advanced_visible','1',2,'article','yesnoradio',0,'sam'),(1,'image_sort_column','id',2,'image','',0,'sam'),(1,'image_sort_dir','desc',2,'image','',0,'sam'),(1,'pane_article_meta_visible','1',2,'article','yesnoradio',0,'calwilson'),(1,'arc_twitter_tab','extensions',2,'arc_twitter','arc_twitter_tab_select',0,''),(1,'arc_short_site_url','sampology.com',2,'arc_twitter','text_input',0,''),(1,'arc_short_url','0',2,'arc_twitter','yesnoRadio',0,''),(1,'arc_twitter_url_method','full',2,'arc_twitter','arc_twitter_url_method_select',0,''),(1,'arc_twitter_tweet_default','1',2,'arc_twitter','yesnoRadio',0,''),(1,'thumb_w','',2,'image','text_input',0,''),(1,'thumb_h','',2,'image','text_input',0,''),(1,'thumb_crop','',2,'image','text_input',0,''),(1,'plugin_sort_column','name',2,'plugin','',0,'calwilson'),(1,'plugin_sort_dir','asc',2,'plugin','',0,'calwilson'),(1,'plugin_sort_column','name',2,'plugin','',0,'sam'),(1,'plugin_sort_dir','asc',2,'plugin','',0,'sam'),(1,'arc_twitter_suffix','',2,'arc_twitter','text_input',0,''),(1,'arc_twitter_cache_dir','/home/youngstr/public_html/sampology.com/textpattern/home/youngstr/public_html/sampology.com/textpattern/tmp',1,'arc_twitter','text_input',0,''),(1,'pane_article_more_visible','1',2,'article','yesnoradio',0,'calwilson'),(1,'image_list_pageby','100',2,'image','text_input',0,'calwilson'),(1,'discuss_sort_column','date',2,'discuss','',0,'calwilson'),(1,'discuss_sort_dir','desc',2,'discuss','',0,'calwilson'),(1,'values_ordering','custom',10,'glz_custom_f','text_input',0,''),(1,'multiselect_size','5',10,'glz_custom_f','text_input',0,''),(1,'datepicker_url','http://sampology.com/scripts/glz_custom_fields/jquery.datePicker/',10,'glz_custom_f','text_input',0,''),(1,'datepicker_format','dd/mm/yyyy',10,'glz_custom_f','text_input',0,''),(1,'datepicker_first_day','1',10,'glz_custom_f','text_input',0,''),(1,'datepicker_start_date','01/01/1990',10,'glz_custom_f','text_input',0,''),(1,'timepicker_url','http://sampology.com/scripts/glz_custom_fields/jquery.timePicker/',10,'glz_custom_f','text_input',0,''),(1,'timepicker_start_time','00:00',10,'glz_custom_f','text_input',0,''),(1,'timepicker_end_time','23:30',10,'glz_custom_f','text_input',0,''),(1,'timepicker_step','30',10,'glz_custom_f','text_input',0,''),(1,'timepicker_show_24','true',10,'glz_custom_f','text_input',0,''),(1,'custom_scripts_path','/home/youngstr/public_html/sampology.com/scripts/glz_custom_fields/',10,'glz_custom_f','text_input',0,''),(1,'pane_article_advanced_visible','1',2,'article','yesnoradio',0,'calwilson'),(1,'log_sort_column','time',2,'log','',0,'calwilson'),(1,'log_sort_dir','desc',2,'log','',0,'calwilson'),(1,'admin_sort_column','name',2,'admin','',0,'sam'),(1,'admin_sort_dir','asc',2,'admin','',0,'sam'),(1,'article_list_pageby','100',2,'list','text_input',0,'calwilson'),(1,'log_sort_column','time',2,'log','',0,'sam'),(1,'log_sort_dir','desc',2,'log','',0,'sam'),(1,'discuss_sort_column','date',2,'discuss','',0,'sam'),(1,'discuss_sort_dir','desc',2,'discuss','',0,'sam'),(1,'pane_form_article_visible','1',2,'form','yesnoradio',0,'calwilson'),(1,'pane_article_image_visible','1',2,'article','yesnoradio',0,'calwilson'),(1,'pane_article_image_visible','1',2,'article','yesnoradio',0,'sam'),(1,'pane_article_custom_field_visible','1',2,'article','yesnoradio',0,'sam'),(1,'file_sort_column','filename',2,'file','',0,'sam'),(1,'file_sort_dir','asc',2,'file','',0,'sam'),(1,'link_sort_column','name',2,'link','',0,'sam'),(1,'link_sort_dir','asc',2,'link','',0,'sam'),(1,'article_list_pageby','100',2,'list','text_input',0,'sam'),(1,'pane_article_custom_field_visible','1',2,'article','yesnoradio',0,'calwilson'),(1,'pane_form_comment_visible','1',2,'form','yesnoradio',0,'calwilson'),(1,'pane_form_file_visible','1',2,'form','yesnoradio',0,'calwilson'),(1,'pane_form_link_visible','1',2,'form','yesnoradio',0,'calwilson'),(1,'pane_form_section_visible','1',2,'form','yesnoradio',0,'calwilson'),(1,'image_list_pageby','100',2,'image','text_input',0,'sam'),(1,'link_sort_column','name',2,'link','',0,'georgie'),(1,'link_sort_dir','asc',2,'link','',0,'georgie'),(1,'article_sort_column','posted',2,'list','',0,'georgie'),(1,'article_sort_dir','desc',2,'list','',0,'georgie'),(1,'pane_form_link_visible','1',2,'form','yesnoradio',0,'georgie'),(1,'pane_form_misc_visible','1',2,'form','yesnoradio',0,'georgie'),(1,'pane_form_section_visible','1',2,'form','yesnoradio',0,'georgie'),(1,'pane_form_file_visible','0',2,'form','yesnoradio',0,'georgie'),(1,'pane_form_comment_visible','0',2,'form','yesnoradio',0,'georgie'),(1,'pane_form_article_visible','0',2,'form','yesnoradio',0,'georgie'),(1,'pane_article_meta_visible','1',2,'article','yesnoradio',0,'georgie'),(1,'pane_article_image_visible','1',2,'article','yesnoradio',0,'georgie'),(1,'pane_article_custom_field_visible','1',2,'article','yesnoradio',0,'georgie'),(1,'admin_sort_column','name',2,'admin','',0,'georgie'),(1,'admin_sort_dir','asc',2,'admin','',0,'georgie'),(1,'pane_article_recent_visible','1',2,'article','yesnoradio',0,'sam'),(1,'admin_sort_column','name',2,'admin','',0,'paul'),(1,'admin_sort_dir','asc',2,'admin','',0,'paul'),(1,'pane_page_article-tags_visible','1',2,'page','yesnoradio',0,'paul'),(1,'pane_page_nav-tags_visible','1',2,'page','yesnoradio',0,'paul'),(1,'pane_page_misc-tags_visible','1',2,'page','yesnoradio',0,'paul'),(1,'pane_page_xml-tags_visible','1',2,'page','yesnoradio',0,'paul'),(1,'pane_page_article-nav-tags_visible','1',2,'page','yesnoradio',0,'paul'),(1,'pane_page_file-tags_visible','1',2,'page','yesnoradio',0,'paul'),(1,'plugin_sort_column','name',2,'plugin','',0,'paul'),(1,'plugin_sort_dir','asc',2,'plugin','',0,'paul'),(1,'pane_article_textile_help_visible','1',2,'article','yesnoradio',0,'paul'),(1,'pane_article_advanced_visible','1',2,'article','yesnoradio',0,'paul'),(1,'pane_article_custom_field_visible','0',2,'article','yesnoradio',0,'paul'),(1,'pane_article_image_visible','1',2,'article','yesnoradio',0,'paul'),(1,'pane_article_meta_visible','1',2,'article','yesnoradio',0,'paul'),(1,'article_sort_column','section',2,'list','',0,'paul'),(1,'article_sort_dir','desc',2,'list','',0,'paul'),(1,'link_sort_column','name',2,'link','',0,'paul'),(1,'link_sort_dir','asc',2,'link','',0,'paul'),(1,'discuss_sort_column','date',2,'discuss','',0,'paul'),(1,'discuss_sort_dir','desc',2,'discuss','',0,'paul'),(1,'pane_form_section-tags_visible','1',2,'form','yesnoradio',0,'paul'),(1,'pane_form_category-tags_visible','1',2,'form','yesnoradio',0,'paul'),(1,'pane_form_file-tags_visible','1',2,'form','yesnoradio',0,'paul'),(1,'pane_form_search-result-tags_visible','1',2,'form','yesnoradio',0,'paul'),(1,'pane_form_comment-form-tags_visible','1',2,'form','yesnoradio',0,'paul'),(1,'pane_form_comment-detail-tags_visible','1',2,'form','yesnoradio',0,'paul'),(1,'pane_form_article-tags_visible','1',2,'form','yesnoradio',0,'paul'),(1,'pane_form_link-tags_visible','1',2,'form','yesnoradio',0,'paul'),(1,'pane_form_comment-tags_visible','1',2,'form','yesnoradio',0,'paul'),(1,'pane_form_section_visible','1',2,'form','yesnoradio',0,'paul'),(1,'pane_form_misc_visible','1',2,'form','yesnoradio',0,'paul'),(1,'article_list_pageby','100',2,'list','text_input',0,'paul'),(1,'pane_article_more_visible','1',2,'article','yesnoradio',0,'paul'),(1,'log_sort_column','time',2,'log','',0,'paul'),(1,'log_sort_dir','desc',2,'log','',0,'paul'),(1,'pane_form_article_visible','1',2,'form','yesnoradio',0,'paul'),(1,'pane_form_link_visible','1',2,'form','yesnoradio',0,'paul'),(1,'arc_twitter_prefix','Just posted:',2,'arc_twitter','text_input',0,''),(1,'arc_twitter_user','',1,'arc_twitter','text_input',0,''),(1,'file_sort_column','filename',2,'file','',0,'paul'),(1,'file_sort_dir','asc',2,'file','',0,'paul'),(1,'pane_page_nav-tags_visible','1',2,'page','yesnoradio',0,'sam'),(1,'smd_thumb_backup_w','100',2,'smd_thumb','text_input',0,''),(1,'smd_thumb_backup_h','100',2,'smd_thumb','text_input',1,''),(1,'smd_thumb_backup_c','1',2,'smd_thumb','text_input',2,''),(1,'doctype','xhtml',0,'publish','doctypes',190,''),(1,'publisher_email','',1,'admin','text_input',115,''),(1,'last_update_check','a:2:{s:3:\"msg\";s:158:\"New Textpattern version 4.6.2 <a href=\"http://textpattern.com/download\" rel=\"external\" title=\"Go to the Textpattern download area\">available for download</a>.\";s:4:\"when\";i:1515591509;}',2,'publish','text_input',0,''),(1,'pane_smd_thumb_group_type','all',2,'smd_thumb','text_input',0,'calwilson'),(1,'section_sort_column','time',2,'section','',0,'sam'),(1,'section_sort_dir','desc',2,'section','',0,'sam');

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

CREATE TABLE `txp_section` (
  `name` varchar(128) NOT NULL DEFAULT '',
  `page` varchar(128) NOT NULL DEFAULT '',
  `css` varchar(128) NOT NULL DEFAULT '',
  `in_rss` int(2) NOT NULL DEFAULT '1',
  `on_frontpage` int(2) NOT NULL DEFAULT '1',
  `searchable` int(2) NOT NULL DEFAULT '1',
  `title` varchar(255) NOT NULL DEFAULT '',
  PRIMARY KEY (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 PACK_KEYS=1;

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

INSERT INTO `txp_section` VALUES ('about','default','default',0,0,1,'About'),('default','default','default',1,1,1,'default'),('music','default','default',1,0,1,'Music'),('video','default','default',1,0,1,'Video'),('dates','default','default',1,0,1,'Dates'),('gallery','default','default',1,0,1,'Gallery'),('blog','default','default',1,1,1,'Blog'),('contact','default','default',1,0,1,'Contact'),('search','default','default',0,0,0,'search'),('releases','default','default',0,0,1,'Releases'),('touring','default','default',1,1,1,'touring');

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

CREATE TABLE `txp_users` (
  `user_id` int(4) NOT NULL AUTO_INCREMENT,
  `name` varchar(64) NOT NULL DEFAULT '',
  `pass` varchar(128) NOT NULL,
  `RealName` varchar(255) NOT NULL DEFAULT '',
  `email` varchar(254) NOT NULL DEFAULT '',
  `privs` tinyint(2) NOT NULL DEFAULT '1',
  `last_access` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `nonce` varchar(64) NOT NULL DEFAULT '',
  PRIMARY KEY (`user_id`),
  UNIQUE KEY `name` (`name`)
) ENGINE=MyISAM AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 PACK_KEYS=1;

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

INSERT INTO `txp_users` VALUES (1,'calwilson','$P$BBmm1vmf.chRgcFrL43kYJx9kcjYiv/','Cal Wilson','cal@theworkingparty.com.au',1,'2017-03-19 01:40:53','e51dbf949895a413876386f52640008f'),(3,'sam','$P$BO0yLtlaZu79K4WCmqlRyHGVdPaGLS0','Sampology','sampologymusic@gmail.com',2,'2018-01-10 01:31:08','20e1f30e619d10bf8a3e2d3a35746990'),(5,'georgie','$P$Bi7g/hq7hseCovHq0Bu2wS1EWREHGY0','Georgina Luhur','georgina@theworkingparty.com.au',1,'2012-08-22 09:37:09','82b8f92b82f6d6da6181fa5009f32cd6'),(6,'paul','$P$BWfylJPRCQ9LtSh3pY/LFKiEx86yWQ0','Paul Jaques','hello@pauljaques.net',1,'2014-10-18 21:12:25','fdff278770eb656702ee5d411ac051f4');


