# PLUGIN PREVIEW BY TEXTPATTERN.INFO
//<?php
/**
* smd_tags: A Textpattern CMS plugin for unlimited, structured taxonomy across content types.
*
* Features:
* Article, image, file and link tags
* Rapid tag creation / editing / multi-editing
* Optionally import tags from rss_uc, tru_tags, category1/2 or custom fields
* Create tag lists, trees and clouds
* Find related content based on tag
* Tag combinators from the URL
*
* @author Stef Dawson
* @link http://stefdawson.com/
*
* @todo
* Update for 4.6.x:
* Inputs surrounded with graf()
* Table class alteration and listtable wrapper
* Prefs screen to use inputLabel() and new wrapper div
* Alter tag pool layout to allow collapsing groups of parent->children
* Use article_row_info(), link_format_info()
* Investigate storing tag data as jQuery .data() instead of in <span>s (from Txp 4.6+ it can be done in HTML data-* attributes)
* Pagination of tag lists / related content
* Feed handler prefs to inject tags into RSS+Atom (like tru_tags: code at bottom of this plugin)
* Add option to display tag lists AND TextArea(+) on list pages
* See if rapid tagging can be made more intuitive wrt Create/Save to avoid clobbering already created tags (from pieman http://forum.textpattern.com/viewtopic.php?pid=240099#p240099)
* When refreshing the Manage tab, FF remembers the Title and tries to add it on page load, throwing a msgarea warning about the tag already exists (or that name is empty)
* Possibility of section tags? (from jakob)
* Per-section / per-category tag counts
* Query tags without section/article context (Dwayne: http://forum.textpattern.com/viewtopic.php?pid=246750#p246750)
* On list pages:
* AJAX fetch of the tag set for TextArea and TextArea+ modes to improve page load time (plus we then only grab the ones in the current category hierarchy)
* Option for bi-directional lookup of tags from the current category, i.e. grandparents and children (from aswihart http://forum.textpattern.com/viewtopic.php?pid=232445#p232445)
* Add autocomplete options to smd_tags prefs so autofill etc can be configured (from aswihart)
* Check what happens when deleting content (e.g. images): it sometimes triggers " '' is not an Integer " assertion (from curiouz http://forum.textpattern.com/viewtopic.php?pid=236023#p236023)
* Perhaps offer sort option for List mode to a) retain hierarchy, b) strict alphabetical, c) group them somehow to show how sets of tags are related (from pieman)
* Use head_end step for style block
* Upgrade prefs to use a smd_tags_get_prefs() function
* @tofix
* Sanitize tags from custom fields / cats during import
* Blob of output from AJAX (cf. "If you spot such JavaScript responses your plugin breaks AJAX at the write tab, for instance by echo()ing something as text/html when send_script_response() with text/javascript would be the proper reply with the expected mime type.")
* Query count -- cache tag sets and verify URL tag lists are as optimum as possible (see smd_bio)
* Live search sometimes gives wrong results when group tags contain no (or very few) children
* Verify limit/offset behaviour (giz: http://forum.textpattern.com/viewtopic.php?pid=244020#p244020)
* smd_tags_context() : Invalid argument supplied for foreach() (frickinmuck: http://forum.textpattern.com/viewtopic.php?pid=249250#p249250)
* Deal with parent attribute in smd_tag_name better (stop it showing 'root'?)
* Detect scenario when article saves fail and don't update tags under these circumstances (e.g. when concurrent users are editing the same document)
*/
// ------------------------
// ADMIN SIDE CODE
if (@txpinterface === 'admin') {
add_privs('smd_tags','1,2');
add_privs('smd_tags_users','1,2,3,4,5,6');
add_privs('plugin_prefs.smd_tags','1,2');
// Extensions tab
register_tab('extensions', 'smd_tags', gTxt('smd_tags'));
register_callback('smd_tags', 'smd_tags');
register_callback('smd_tags_users', 'smd_tags_users');
// Note the callbacks are ordered with 'save' first
register_callback('smd_tags_multi_edit', 'list', 'list_multi_edit');
register_callback('smd_tags_savelist', 'article', 'create');
register_callback('smd_tags_savelist', 'article', 'edit');
register_callback('smd_tags_loadlist', 'article');
register_callback('smd_tags_multi_edit', 'image', 'image_delete');
register_callback('smd_tags_savelist', 'image', 'image_save');
register_callback('smd_tags_loadlist', 'image');
register_callback('smd_tags_multi_edit', 'file', 'file_delete');
register_callback('smd_tags_savelist', 'file', 'file_save');
register_callback('smd_tags_loadlist', 'file');
register_callback('smd_tags_multi_edit', 'link', 'link_multi_edit');
register_callback('smd_tags_savelist', 'link', 'link_post');
register_callback('smd_tags_savelist', 'link', 'link_save');
register_callback('smd_tags_loadlist', 'link');
register_callback('smd_tags_setup', 'plugin_prefs.smd_tags');
register_callback('smd_tags_welcome', 'plugin_lifecycle.smd_tags');
global $smd_tag_prefs, $smd_tags_styles;
// Triple array, output in alphabetical order, sequentially numbered
// 0: list of current prefs + default
// 1: list of legacy prefs that may need removing (default should all be empty)
// 2: position that a heading should be inserted + heading itself
$smd_tag_prefs = array(
array(
'smd_tag_p_enable' => '1111', // one digit for article/image/file/link. 1=enabled; 0=disabled
'smd_tag_p_input' => '0', // 0=select, 1=links, 2=textarea, 3=textarea+
'smd_tag_p_lbi' => '0',
'smd_tag_p_linkcat' => '0',
'smd_tag_p_listpar' => '1', // permit parent in tag tree on content pages
'smd_tag_p_master' => '', // special 'global' category (per type: same name for each)
'smd_tag_p_qtag' => '0', // 0=no autocomplete, 1=std a/c, 2=strict a/c
'smd_tag_p_qtpath' => '', // Dir to js file(s) relative to textpattern dir
'smd_tag_p_qtstyl' => '', // Dir to css file(s)
'smd_tag_p_size' => '6',
'smd_tag_t_astart' => '0',
'smd_tag_t_auto' => '1',
'smd_tag_t_cols' => '1',
'smd_tag_t_colsord' => '0',
'smd_tag_t_count' => '1',
'smd_tag_t_deltree' => '0',
'smd_tag_t_delused' => '0',
'smd_tag_t_desc_textile' => '1',
'smd_tag_t_desc_tooltip' => '1',
'smd_tag_t_enrep' => '1',
'smd_tag_t_hilite' => '91919a',
'smd_tag_t_hover' => 'aaa',
'smd_tag_t_indent' => '—',
'smd_tag_t_mdelim' => ',',
'smd_tag_t_size_desc' => '240, 80',
'smd_tag_u_combi' => '1',
'smd_tag_u_combi_and' => '+',
'smd_tag_u_combi_or' => '|',
'smd_tag_u_pnam' => 'smd_tag',
'smd_tag_u_ptyp' => 'smd_tagtype',
'smd_tag_u_sec' => 'smd_tags',
),
array(
'smd_tag_p_first' => '1',
),
array(
1 => gTxt('smd_tag_prefs_p'),
10 => gTxt('smd_tag_prefs_t'),
26 => gTxt('smd_tag_prefs_u'),
)
);
// CSS definitions: hopefully kind to themers
$smd_tags_styles = array(
'common' =>
'.smd_hidden { display:none; }',
'tag_manager' =>
'.smd_hover { cursor:pointer; background:#{hov}; }
.smd_current { padding:3px; font-weight:bold; background:#{cur}; }
.smd_tags_showpars, .smd_tags_linkcat { max-width:12em; }
#smd_tag_filt { border:1px solid grey; font-size:90%; float:right; margin:-3.5em 1em 0; width:240px; padding:1em 2em; box-shadow: 4px 4px 5px #999; }
#smd_tag_multisel { margin:10px 0 0; }
.smd_freeform_byrow { float:left; padding:5px 2px; }
.smd_freeform_bycol { float:left; padding:1px 5px; }
.smd_freeform_bycol li, .smd_freeform_bycol li { padding:2px; }
.smd_clear { clear:both; }
.smd_tags_new label {display:block; margin:2px 1px; }
.smd_tags_new input[type="submit"] { margin:1.5em 0 0; }
.smd_tags_input_group { float:left; margin:0 1em; }
.smd_tags_pool { clear:both; padding:1em 0;}',
'list_pages' =>
'.smd_hidden { display:none; }
.smd_fakebtn, #smd_tags_bylink span { cursor:pointer; }
.smd_tagip { margin:.4em 0 .8em; max-width:400px; }
.smd_sel { font-weight:bold; }
.smd_tooltip { position:absolute; background:#eee; max-width:300px; min-width:150px; border:1px solid black; padding:1em; box-shadow:5px 5px 4px #999; }',
'report' =>
'#smd_tag_report_pane { display:none; position:absolute; left:200px; max-width:500px; border:3px ridge #999; opacity:.92; filter:alpha(opacity:92); padding:15px 20px; background-color:#e2dfce; color:#80551e; }
#smd_tag_report_pane .publish { float:right; }',
);
} elseif (txpinterface === 'public') {
if (class_exists('\Textpattern\Tag\Registry')) {
Txp::get('\Textpattern\Tag\Registry')
->register('smd_tag_list')
->register('smd_tag_name')
->register('smd_tag_count')
->register('smd_tag_info')
->register('smd_if_tag')
->register('smd_if_tag_list')
->register('smd_related_tags');
}
}
if (!defined('SMD_TAG')) {
define("SMD_TAG", 'smd_tags');
}
if (!defined('SMD_TAGC')) {
define("SMD_TAGC", 'smd_tags_cat');
}
if (!defined('SMD_TAGU')) {
define("SMD_TAGU", 'smd_tags_used');
}
register_callback('smd_tags_url_handler', 'pretext');
// -------------------------------------------------------------
function smd_tags_welcome($evt, $stp) {
$msg = '';
switch ($stp) {
case 'installed':
smd_tags_table_install(0);
$msg = 'Happy tagging!';
break;
case 'deleted':
smd_tags_prefs_remove('', 0);
break;
}
return $msg;
}
// ------------------------
function smd_tags($evt, $stp) {
$available_steps = array(
'smd_tag_catlist' => true,
'smd_tag_create' => true,
'smd_tag_parentlist' => true,
'smd_tag_save' => true,
'smd_tags_delete' => true,
'smd_tags_import' => true,
'smd_tags_import_one' => true,
'smd_tags_manage' => false,
'smd_tags_multi_set_parent' => true,
'smd_tags_multi_catlink' => true,
'smd_tags_prefs_show' => false,
'smd_tags_prefs_install' => true,
'smd_tags_prefs_remove' => true,
'smd_tags_prefs_update' => true,
'smd_tags_sync' => false,
'smd_tags_table_install' => false,
'smd_tags_table_remove' => true,
'smd_tags_table_rebuild' => true,
);
if ($stp == 'save_pane_state') {
smd_tags_save_pane_state();
} else if (!$stp or !bouncer($stp, $available_steps)) {
$pref = smd_tags_pref_get('smd_tag_t_astart');
$stp = 'smd_tags_prefs_show';
if ($pref) {
switch ($pref['smd_tag_t_astart']['val']) {
case 1:
$stp = 'smd_tags_manage';
break;
case 0:
default:
$stp = 'smd_tags_prefs_show';
}
}
}
$stp();
}
// ------------------------
function smd_tags_users($evt, $stp) {
$available_steps = array(
'smd_tag_catlist' => true,
'smd_tag_parentlist' => true,
'smd_tag_get_desc' => true,
);
if (!$stp or !bouncer($stp, $available_steps)) {
// Do nothing
} else {
$stp();
}
}
// ------------------------
// Create dropdown and populate it with tags of the relevant type, highlighting any $sel tags
function smd_multiTreeSelectInput($select_name = '', $tree = '', $sel = '') {
$pref = smd_tags_pref_get(array('smd_tag_p_size', 'smd_tag_p_listpar'), 1);
$rows = $pref['smd_tag_p_size']['val'];
$incl = $pref['smd_tag_p_listpar']['val'];
$out[] = '<select id="'.$select_name.'" name="'.$select_name.'[]" class="list" size="'. $rows .'"' .(($rows==1) ? '' : ' multiple="multiple"'). '>';
foreach ($tree as $leaf) {
if ($leaf['name'] == 'root') continue;
$selected='';
foreach ($sel as $gid => $item) {
if ($leaf['id'] == $item['tag_id']) {
$selected = ' selected="selected"';
continue;
}
}
$leaf['name'] = htmlspecialchars($leaf['name']);
$indent = ($leaf['level'] > 0) ? str_repeat(sp.sp,$leaf['level']) : '';
$selectable = (!$incl && $leaf['children'] > 0) ? ' disabled="disabled"' : '';
$out[] = t.'<option value="'.$leaf['id'].'"'.$selected.$selectable.'>'.$indent.htmlspecialchars($leaf['title']).'</option>';
}
$out[] = '</select>';
return join('',$out);
}
// ------------------------
// Called whenever one of the 4 main tabs are used on the admin side. Inserts the input method if required
function smd_tags_loadlist($evt, $stp) {
global $smd_tags_styles, $app_mode, $step;
if (smd_tags_table_exist()) {
$ctrls = smd_tags_pref_get(array('smd_tag_p_enable', 'smd_tag_p_input', 'smd_tag_p_size', 'smd_tag_p_qtag', 'smd_tag_p_qtpath', 'smd_tag_p_qtstyl', 'smd_tag_p_linkcat', 'smd_tag_t_desc_tooltip'), 1);
$onoff = smd_tags_pref_explode($ctrls['smd_tag_p_enable']['val']);
$iptyp = $ctrls['smd_tag_p_input']['val'];
$selsz = $ctrls['smd_tag_p_size']['val'];
$quick = $ctrls['smd_tag_p_qtag']['val'];
$jsdir = $ctrls['smd_tag_p_qtpath']['val'];
$csdir = $ctrls['smd_tag_p_qtstyl']['val'];
$clink = $ctrls['smd_tag_p_linkcat']['val'];
$toolt = $ctrls['smd_tag_t_desc_tooltip']['val'];
$jstail = ($jsdir) ? '/' : '';
$cstail = ($csdir) ? '/' : '';
$addIt = false;
$itemID = smd_getID();
$edtBtn = has_privs('smd_tags') ? ' ['.eLink('smd_tags', 'smd_tags_manage', 'smd_tag_type', $evt, gTxt('edit')).'] ' : '';
$tagtop = gTxt('smd_tag_title').$edtBtn;
$selist = '<span id="smd_tags" name="smd_tags_bylist" class="smd_tagip"></span>';
$selhid = '<span id="smd_tags" name="smd_tags_bylist" class="smd_hidden"></span>';
$tooltip = ($toolt) ? '<span id="smd_tag_tt" class="smd_tooltip smd_hidden"></span>' : '';
$widget = '';
switch ($iptyp) {
case 0:
$ipmeth = $selist;
break;
case 1:
$ipmeth = '<span name="smd_tags_bylink" id="smd_tags_bylink" class="smd_tagip"></span>'.$selhid;
break;
case 2:
case 3:
$ipmeth = '<textarea name="smd_tags_bytext" rows="'.$selsz.'" class="smd_tagip"></textarea>'.$selhid;
break;
}
switch ($evt) {
case "article":
$addIt = ($onoff[gTxt('tab_list')] == 1) ? true : false;
if ($addIt) {
extract(gpsa(array('view','from_view')));
$view = ($view) ? $view : "text";
$grabcats = 'jQuery("#category-1 option:selected, #category-2 option:selected")';
$jsElem = 'jQuery(".category-2").parent()';
$trigger = 'jQuery("#category-1, #category-2")';
if ($view != 'text') {
$widget = '';
} else {
$widget = escape_js('<div>'.$tagtop.'</div>' . $ipmeth);
}
}
break;
case "image":
if ($step == "image_edit") {
$addIt = ($onoff[gTxt('tab_image')] == 1) ? true : false;
if ($addIt) {
$grabcats = 'jQuery("#image_category option:selected")';
$jsElem = 'jQuery(".edit-image-category")';
$trigger = 'jQuery("#image_category")';
$widget = escape_js(inputLabel('smd_tags', $ipmeth, $tagtop));
}
}
break;
case "file":
if ($step == "file_edit") {
$addIt = ($onoff[gTxt('tab_file')] == 1) ? true : false;
if ($addIt) {
$grabcats = 'jQuery("#file_category option:selected")';
$jsElem = 'jQuery(".edit-file-category")';
$trigger = 'jQuery("#file_category")';
$widget = escape_js(inputLabel('smd_tags', $ipmeth, $tagtop));
}
}
break;
case "link":
if ($step == "link_edit") {
$addIt = ($onoff[gTxt('tab_link')] == 1) ? true : false;
if ($addIt) {
$grabcats = 'jQuery("#link_category option:selected")';
$jsElem = 'jQuery(".edit-link-category")';
$trigger = 'jQuery("#link_category")';
$widget = escape_js(inputLabel('smd_tags', $ipmeth, $tagtop));
}
}
break;
}
// Don't re-serve the css+javascript if this is an AJAX save
if ($app_mode == 'async') {
$addIt = false;
}
// Add the dropdown and js toggle/clear options if required
if ($addIt) {
$qs = array(
"event" => "smd_tags_users",
);
$qsVars = "index.php".join_qs($qs);
if ($quick > 0) {
echo <<<EOJS
<script type="text/javascript" src="{$jsdir}{$jstail}jquery.autocomplete.pack.js"></script>
<link rel="stylesheet" type="text/css" href="{$csdir}{$cstail}jquery.autocomplete.css"></script>
EOJS;
}
// Inject styles
echo '<style type="text/css">'. $smd_tags_styles['list_pages']. '</style>';
echo script_js(<<<EOJS
jQuery(function() {
{$jsElem}.after('{$widget}{$tooltip}');
function smd_tagList(typ) {
var grabcats = [];
{$grabcats}.each(function() {
if (jQuery(this).val() != '') {
grabcats.push(jQuery(this).val());
}
});
grabcats = grabcats.join(",");
var smd_link_mode = (('{$clink}'=='0') ? '2' : ((grabcats=='') ? 1 : ''));
jQuery.post('{$qsVars}', { step: "smd_tag_parentlist", name: 'smd_tag_parent', type: typ, cat: grabcats, link_mode: smd_link_mode, itemid: "{$itemID}", _txp_token: textpattern._txp_token },
function(data) {
jQuery("#smd_tags").html(data);
jQuery("#smd_tags_bylink").empty();
var smd_tagpool = [];
var smd_tagsel = [];
var smd_taglinks = [];
jQuery('#smd_tags select option').each(function() {
var curr = jQuery(this);
var txt = jQuery.trim(curr.text());
smd_tagpool.push(txt);
if (jQuery("#smd_tags_bylink").text() != "") {
jQuery("#smd_tags_bylink").append(" · ");
}
if (curr.prop("selected") == true) {
smd_tagsel.push(txt);
jQuery("#smd_tags_bylink").append('<span class="smd_sel">'+txt+'</span>');
} else {
jQuery("#smd_tags_bylink").append('<span>'+txt+'</span>');
}
});
jQuery("#smd_tags_bylink span").click(function() {
jQuery(this).toggleClass("smd_sel");
var ltxt = jQuery(this).text();
jQuery("#smd_tags select option").each(function() {
var curr = jQuery(this);
if (jQuery.trim(curr.text()) == ltxt) {
this.selected = !this.selected;
}
});
});
jQuery("textarea[name='smd_tags_bytext']").val(smd_tagsel.join(", "));
if ({$quick} > 0) {
jQuery("textarea[name='smd_tags_bytext']").autocomplete(smd_tagpool, {
multiple: true,
mustMatch: (($quick==2)?true:false),
autoFill: true
});
}
jQuery("textarea[name='smd_tags_bytext']").keyup(function() {
smd_tagsResyncSelect({$quick});
});
jQuery('#smd_clr').click(function() {
jQuery('#smd_tags select option').prop("selected", "");
});
jQuery('#smd_tog').click(function() {
jQuery('#smd_tags select option').each(function() {
this.selected = !this.selected;
});
});
// Tooltips
if ('{$toolt}' == '1') {
jQuery('#smd_tags select, #smd_tags_bylink').hover( function(ev) {
// Do nothing
}, function() {
// Hide tooltip when exiting the entire tag area
jQuery('.smd_tooltip').hide('fast');
});
jQuery('#smd_tags select option, #smd_tags_bylink span').hover( function(ev) {
var thisTag = jQuery(this).val();
if (!thisTag) {
thisTag = jQuery(this).html();
}
if (jQuery('#smd_tag_tt').data(thisTag)) {
smd_tags_show_desc(ev, jQuery('#smd_tag_tt').data(thisTag));
} else {
jQuery.post('{$qsVars}',
{
step: "smd_tag_get_desc",
tag_ref: thisTag,
_txp_token: textpattern._txp_token
},
function(data) {
jQuery('#smd_tag_tt').data(thisTag, data);
smd_tags_show_desc(ev, jQuery('#smd_tag_tt').data(thisTag));
});
}
});
}
}
);
}
function smd_tags_show_desc(ev, content) {
jQuery('#smd_tag_tt').hide().empty().append(content).show();
jQuery('.smd_tooltip').css('left', ev.pageX - 60).css('top', ev.pageY + 15).css('display', 'block');
}
smd_tagList('{$evt}');
if ('{$clink}'=='1') {
{$trigger}.change(function() {
smd_tagList('{$evt}');
});
}
function smd_tagsResyncSelect(lvl) {
var tbTags = jQuery("textarea[name='smd_tags_bytext']").val().split(/,\s+?/);
jQuery("#smd_tags select option").each(function() {
var curr = jQuery(this);
if(jQuery.inArray(jQuery.trim(curr.text()), tbTags) == -1) {
curr.prop("selected", "");
} else {
curr.prop("selected", true);
}
});
}
});
EOJS
);
}
}
}
// ------------------------
// Update the tags used table if some tags have been added/saved
function smd_tags_savelist($evt, $stp) {
if (smd_tags_table_exist()) {
$ctrls = smd_tags_pref_get(array('smd_tag_p_qtag', 'smd_tag_p_input', 'smd_tag_p_linkcat', 'smd_tag_p_master'), 1);
$quick = $ctrls['smd_tag_p_qtag']['val'];
$iptyp = $ctrls['smd_tag_p_input']['val'];
$clink = $ctrls['smd_tag_p_linkcat']['val'];
$god = $ctrls['smd_tag_p_master']['val'];
$god_clause = ($god == '') ? '' : " AND parent = '" . doSlash($god) . "'";
$itemID = smd_getID();
if ($itemID) {
// Can't use the 'edit' step because otherwise save triggers on article page load;
// hence, the 'save' button state is used instead. Also, on article creation, the global
// step is set to 'edit' by the time the plugin is hit, so we hack the step if the 'publish'
// button was hit
$stp = (ps('publish')) ? 'publish' : $stp;
$saveSteps = array('create', 'publish', 'image_save', 'file_save', 'link_save', 'link_post');
if (ps('save') || in_array($stp, $saveSteps)) {
safe_delete(SMD_TAGU, 'item_id="'.$itemID.'" AND type="'.$evt.'"');
$tags = ps('smd_tag_parent') ? ps('smd_tag_parent') : array();
$allowedTags = array();
if ($god_clause) {
$allowedTags = safe_column('id', SMD_TAG, "type='$evt'" . $god_clause);
}
// Validate the incoming tags to those in the currently selected category|ies
if ($clink) {
$usedCats = ($evt=="article") ? array(gps('Category1'), gps('Category2')) : array(gps('category'));
$catids = safe_column('id', 'txp_category', "name IN (" .join(',', quote_list($usedCats)). ") AND type='$evt'");
$validTags = ($catids) ? safe_column('tag_id', SMD_TAGC, "cat_id IN (" .join(',', quote_list($catids)). ")") : array();
foreach ($validTags as $validTag) {
$rows = smd_tag_tree(safe_field('name',SMD_TAG,"id='".doSlash($validTag)."'"), $evt, '1=1', SMD_TAG);
foreach ($rows as $row) {
$allowedTags[] = $row['id'];
}
}
$tags = array_intersect(array_unique($allowedTags), $tags);
}
$vals = array();
foreach($tags as $val){
safe_insert(SMD_TAGU, 'tag_id = "'.$val.'", item_id="'.$itemID.'", type="'.$evt.'"');
$vals[] = $val;
}
$tagsText = preg_split("/,\s+?/", ps('smd_tags_bytext'));
// If we're in non-STRICT mode and there are any typed tags that are not in the select list, check they exist and make links to them
if ($quick < 2) {
$added = ($vals) ? safe_column("title", SMD_TAG, "id IN ('".join("','",$vals)."') AND type='$evt'") : array();
$tagsText = array_diff($tagsText, $added);
// Only loop over tags that don't appear to be already defined
foreach($tagsText as $tag) {
if ($tag == "") continue;
$exists = safe_field('id', SMD_TAG, "name = '$tag' AND type = '$evt'"); // 1st check
// Textarea+ input system can add (linked) tags with impunity
if ($iptyp == 3 && !$exists) {
$tagBits = explode('-->', $tag);
if (count($tagBits) == 2) {
$tagparent = strtolower(sanitizeForUrl($tagBits[0]));
$tagparent = safe_field('name', SMD_TAG, "name = '$tagparent' AND type = '$evt'"); // Check parent exists for this type
$tagparent = ($tagparent === false) ? 'root' : $tagparent;
$tagchild = $tagBits[1];
} else {
$tagparent = 'root';
$tagchild = $tagBits[0];
}
$sanitag = strtolower(sanitizeForUrl($tagchild));
$already = safe_field('id', SMD_TAG, "name = '$sanitag' AND type = '$evt'"); // Check again because parent-->child will fail 1st check
if ($already === false) {
$exists = safe_insert(SMD_TAG, "name='".$sanitag."', title='$tagchild', parent='$tagparent', type='$evt'");
}
$catfield = ($evt=="article") ? gps('Category1') : gps('category');
if ($clink && $catfield && $exists) {
// Assign new tag to current category (cat1 for articles)
$catid = safe_insert(SMD_TAGC,"tag_id='$exists', cat_id=(SELECT id FROM ".PFX."txp_category WHERE name='$catfield' AND type='$evt')");
}
rebuild_tree_full($evt, SMD_TAG);
}
if ($exists) {
safe_insert(SMD_TAGU, 'tag_id = "'.$exists.'", item_id="'.$itemID.'", type="'.$evt.'"');
}
}
}
}
}
}
}
// ------------------------
// Handle changes to the tags table during multi-item edits
function smd_tags_multi_edit($evt, $stp) {
// In Txp 4.0.6 the only tabs to allow multi-edits are the Article (list) and Link tabs. Cheat for now
if (smd_tags_table_exist()) {
switch ($evt) {
case "list":
case "link":
$method = ps('edit_method');
$selected = ps('selected');
break;
default:
$method = "delete";
$selected = array(ps('id'));
break;
}
// 'list' is a special case that equates to articles
$type = ($evt == "list") ? "article" : $evt;
if ($selected) {
switch ($method) {
case "delete":
$ids = array();
foreach ($selected as $id) {
$id = assert_int($id);
if (safe_delete(SMD_TAGU, 'item_id = '.$id.' AND type="'.$type.'"')) {
$ids[] = $id;
}
}
return join(', ', $ids);
break;
}
}
return '';
}
}
// ------------------------
// Grab the current article/image/file/link ID
function smd_getID() {
if(!empty($GLOBALS['ID'])) { // newly-saved item
$itemID = intval($GLOBALS['ID']);
} else {
$itemID = (gps('ID')) ? gps('ID') : gps('id');
}
return $itemID;
}
// ------------------------
function smd_tags_table_exist($all='0') {
static $smd_tags_table_ok = array();
if (isset($smd_tags_table_ok[$all])) {
return $smd_tags_table_ok[$all];
}
if ($all) {
$tbls = array(SMD_TAG => 9, SMD_TAGC => 2, SMD_TAGU => 3);
$out = count($tbls);
foreach ($tbls as $tbl => $cols) {
if (gps('debug')) {
echo "++ TABLE " . $tbl . " HAS " . count(@safe_show('columns', $tbl)) . " COLUMNS; REQUIRES " . $cols . " ++" . br;
}
if (count(@safe_show('columns', $tbl)) == $cols) {
$out--;
}
}
$ret = ($out===0) ? 1 : 0;
$smd_tags_table_ok[1] = $ret;
return $ret;
} else {
if (gps('debug')) {
echo "++ TABLE " . SMD_TAG . " HAS " . count(@safe_show('columns', SMD_TAG)) . " COLUMNS;";
}
$ret = @safe_show('columns', SMD_TAG);
$smd_tags_table_ok[0] = $ret;
return $ret;
}
}
// ------------------------
// Add tag tables if not already installed
function smd_tags_table_install($showpane='1') {
global $DB;
$debug = gps('debug');
$GLOBALS['txp_err_count'] = 0;
$ret = '';
$sql = array();
$sql[] = "CREATE TABLE IF NOT EXISTS `".PFX.SMD_TAG."` (
`id` int(6) NOT NULL auto_increment,
`name` varchar(64) NOT NULL default '' COLLATE utf8_general_ci,
`type` varchar(64) NOT NULL default '' COLLATE utf8_general_ci,
`parent` varchar(64) NOT NULL default '' COLLATE utf8_general_ci,
`lft` int(6) NOT NULL default '0',
`rgt` int(6) NOT NULL default '0',
`title` varchar(255) NOT NULL default '' COLLATE utf8_general_ci,
`description` text NULL COLLATE utf8_general_ci,
`desc_html` text NULL COLLATE utf8_general_ci,
PRIMARY KEY (`id`)
) ENGINE=MyISAM PACK_KEYS=1 AUTO_INCREMENT=5 CHARACTER SET=utf8";
if (!smd_tags_table_exist()) {
$sql[] = "INSERT INTO `".PFX.SMD_TAG."` VALUES (1, 'root', 'article', '', 1, 2, 'root', 'DO NOT DELETE', NULL)";
$sql[] = "INSERT INTO `".PFX.SMD_TAG."` VALUES (2, 'root', 'link', '', 1, 2, 'root', 'DO NOT DELETE', NULL)";
$sql[] = "INSERT INTO `".PFX.SMD_TAG."` VALUES (3, 'root', 'image', '', 1, 2, 'root', 'DO NOT DELETE', NULL)";
$sql[] = "INSERT INTO `".PFX.SMD_TAG."` VALUES (4, 'root', 'file', '', 1, 2, 'root', 'DO NOT DELETE', NULL)";
}
$sql[] = "CREATE TABLE IF NOT EXISTS `".PFX.SMD_TAGC."` (
`tag_id` int(6) NOT NULL default '0',
`cat_id` int(6) NOT NULL default '0',
PRIMARY KEY (`tag_id`,`cat_id`)
) ENGINE=MyISAM";
$sql[] = "CREATE TABLE IF NOT EXISTS `".PFX.SMD_TAGU."` (
`item_id` int(11) NOT NULL default '0',
`tag_id` int(6) NOT NULL default '0',
`type` varchar(64) NOT NULL default '' COLLATE utf8_general_ci,
PRIMARY KEY (`item_id`,`tag_id`)
) ENGINE=MyISAM CHARACTER SET=utf8";
if($debug) {
dmp($sql);
}
foreach ($sql as $qry) {
$ret = safe_query($qry);
if ($ret===false) {
$GLOBALS['txp_err_count']++;
echo "<b>" . $GLOBALS['txp_err_count'] . ".</b> " . mysqli_error($DB->link) . "<br />\n";
echo "<!--\n $qry \n-->\n";
}
}
// Upgrade table collation if necessary
$ret = getRows("SHOW TABLE STATUS WHERE name IN ('".PFX.SMD_TAG."', '".PFX.SMD_TAGU."')");
if ($ret[0]['Collation'] != 'utf8_general_ci') {
$ret = safe_alter(SMD_TAG, 'CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci', $debug);
}
if ($ret[1]['Collation'] != 'utf8_general_ci') {
$ret = safe_alter(SMD_TAGU, 'CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci', $debug);
}
// Add the description column on upgrade
$flds = getThings('SHOW COLUMNS FROM `'.PFX.SMD_TAG.'`');
if (!in_array('description',$flds)) {
safe_alter(SMD_TAG, "ADD `description` TEXT NULL AFTER `title`", $debug);
}
if (!in_array('desc_html',$flds)) {
safe_alter(SMD_TAG, "ADD `desc_html` TEXT NULL AFTER `description`", $debug);
}
if ($GLOBALS['txp_err_count'] == 0) {
$message = gTxt('smd_tag_tbl_installed');
smd_tags_prefs_install($message, $showpane);
} else {
if ($showpane) {
$message = gTxt('smd_tag_tbl_not_installed');
smd_tags_prefs_show($message);
}
}
}
// ------------------------
// Drop tag tables if in database
function smd_tags_table_remove() {
global $DB;
$ret = '';
$sql = array();
$GLOBALS['txp_err_count'] = 0;
if (smd_tags_table_exist()) {
$sql[] = "DROP TABLE IF EXISTS " .PFX.SMD_TAG. "; ";
$sql[] = "DROP TABLE IF EXISTS " .PFX.SMD_TAGU. "; ";
$sql[] = "DROP TABLE IF EXISTS " .PFX.SMD_TAGC. "; ";
if(gps('debug')) {
dmp($sql);
}
foreach ($sql as $qry) {
$ret = safe_query($qry);
if ($ret===false) {
$GLOBALS['txp_err_count']++;
echo "<b>" . $GLOBALS['txp_err_count'] . ".</b> " . mysqli_error($DB->link) . "<br />\n";
echo "<!--\n $qry \n-->\n";
}
}
}
if ($GLOBALS['txp_err_count'] == 0) {
$message = gTxt('smd_tag_tbl_removed');
smd_tags_prefs_remove($message);
} else {
$message = gTxt('smd_tag_tbl_not_removed');
smd_tags_prefs_show($message);
}
}
// ------------------------
// Rebuild modified preorder tag links
function smd_tags_table_rebuild() {
$types = array('article', 'image', 'file', 'link');
foreach ($types as $tag_type) {
rebuild_tree_full($tag_type, SMD_TAG);
}
$message = gTxt('smd_tag_tbl_rebuilt');
smd_tags_prefs_show($message);
}
// ------------------------
// Add plugin preferences to prefs
function smd_tags_prefs_install($message='', $showpane='1') {
global $smd_tag_prefs;
$ctr = safe_count('txp_prefs', 'event="smd_tags"') + 1;
foreach ($smd_tag_prefs[0] as $pref => $dflt) {
if (!fetch('name','txp_prefs','name',$pref)) {
$id = safe_insert('txp_prefs','prefs_id=1, name='.doQuote(doSlash($pref)).', val='.doQuote(doSlash($dflt)).', event="smd_tags", position="'.$ctr.'"');
$ctr++;
}
}
// Tidy up any legacy prefs
foreach ($smd_tag_prefs[1] as $pref => $dflt) {
if (fetch('name','txp_prefs','name',$pref)) {
$id = safe_delete('txp_prefs','name='.doQuote(doSlash($pref)));
}
}
if ($showpane) {
$message .= gTxt('smd_tag_prefs_installed');
smd_tags_prefs_show($message);
}
}
// ------------------------
// Remove plugin preferences from prefs table.
// Note: both parts of smd_tags_prefs array are iterated over to tidy up any legacy settings
function smd_tags_prefs_remove($message='', $showpane='1') {
global $smd_tag_prefs;
foreach (array_merge($smd_tag_prefs[0],$smd_tag_prefs[1]) as $pref => $dflt) {
if (fetch('name','txp_prefs','name',$pref)) {
$id = safe_delete('txp_prefs','name='.doQuote(doSlash($pref)));
}
}
if ($showpane) {
$message .= gTxt('smd_tag_prefs_removed');
smd_tags_prefs_show($message);
}
}
// ------------------------
// Saves plugin preferences
function smd_tags_prefs_update($message = '') {
global $smd_tag_prefs;
$post = doSlash(stripPost());
foreach ($smd_tag_prefs[0] as $pref => $dflt) {
if (isset($post[$pref])) {
safe_update("txp_prefs", "val = '".$post[$pref]."'", "name = '".doSlash($pref)."' and prefs_id = 1");
}
}
$message .= gTxt('preferences_saved');
smd_tags_prefs_show($message);
}
// ------------------------
// Split a multi-char preference value into an array of keys/values.
// Note: value is passed in and not read directly from the prefs array in this function - intentionally
function smd_tags_pref_explode($val) {
$order = array_values(array(gTxt('tab_list'),gTxt('tab_image'),gTxt('tab_file'),gTxt('tab_link')));
$onoff = array_values(preg_split('//', $val, -1, PREG_SPLIT_NO_EMPTY));
$out = array();
foreach($order as $key1 => $value1) {
$out[(string)$value1] = $onoff[$key1];
}
return $out;
}
// ------------------------
// Get values from prefs - pass either a single key or an array.
// If 2nd arg is set and the pref doesn't exist, read it from the plugin default
function smd_tags_pref_get($keys, $dflt=0) {
global $smd_tag_prefs;
static $smd_tag_prefstore;
$prefout = array();
if (!is_array($keys)) {
$keys = array($keys);
}
// For later looping round defaults
$allkeys = $keys;
// Check the cache and remove any keys we've already fetched
foreach ($keys as $idx => $key) {
if (isset($smd_tag_prefstore[$key])) {
$prefout[$key] = $smd_tag_prefstore[$key];
unset($keys[$idx]);
}
}
if ($keys) {
$prefkeys = doQuote(join("','",doSlash($keys)));
$rs = safe_rows('name,val,html','txp_prefs','name IN ('.$prefkeys.') ORDER BY name');
foreach($rs as $pref) {
$smd_tag_prefstore[$pref['name']] = $prefout[$pref['name']] = array_slice($pref,1);
}
}
if ($dflt) {
foreach ($allkeys as $pref) {
if (!isset($prefout[$pref])) {
$prefout[$pref] = $smd_tag_prefs[0][$pref];
}
}
}
return $prefout;
}
// ------------------------
// Common buttons on the admin panel
function smd_tags_prefs_buttons() {
$ret = array (
'btnPrefsSave' => fInput('submit', 'submit', gTxt('save'), 'publish'),
'btnInstallTbl' => sLink('smd_tags', 'smd_tags_table_install', gTxt('smd_tag_tbl_install_lbl')),
'btnRemoveTbl' => sLink('smd_tags', 'smd_tags_table_remove', gTxt('smd_tag_tbl_remove_lbl')),
'btnRebuildTbl' => sLink('smd_tags', 'smd_tags_table_rebuild', gTxt('smd_tag_tbl_rebuild_lbl')),
'btnInstall' => sLink('smd_tags', 'smd_tags_prefs_install', gTxt('smd_tag_pref_install_lbl')),
'btnRemove' => sLink('smd_tags', 'smd_tags_prefs_remove', gTxt('smd_tag_pref_remove_lbl')),
'btnPrefs' => sLink('smd_tags', 'smd_tags_prefs_show', gTxt('smd_tag_pref_show_lbl')),
'btnManage' => sLink('smd_tags', 'smd_tags_manage', gTxt('smd_tag_manage_lbl')),
'btnSync' => sLink('smd_tags', 'smd_tags_sync', gTxt('smd_tag_sync_lbl')),
'btnSyncGo' => fInput('submit', 'smd_tags_do_import', gTxt('go'), 'publish'),
'btnHelp' => '<a class="pophelp" rel="help" href="?event=plugin'.a.'step=plugin_help'.a.'name=smd_tags">?</a>',
'btnCreate' => fInput('submit', 'smd_tag_create', gTxt('create'), 'publish', '', 'smd_tags_step=this.name;smd_presub()'),
'btnSave' => fInput('submit', 'smd_tag_save', gTxt('update'), '', '', 'smd_tags_step=this.name;smd_presub()'),
'btnDelete' => fInput('submit', 'smd_tags_delete', '×', '', '', 'smd_tags_step=this.name;smd_presub()'),
'btnStyle' => ' style="border:0;height:25px"',
);
return $ret;
}
// ------------------------
// A stub that can be called from a Txp event
function smd_tags_setup($evt='', $stp='', $message='') {
smd_tags_prefs_show();
}
// ------------------------
// Display the prefs panel
function smd_tags_prefs_show($message='') {
global $smd_tag_prefs, $smd_tags_styles;
pagetop(gTxt('smd_tag_prefs_title'),$message);
extract(smd_tags_prefs_buttons());
// Inject styles
echo '<style type="text/css">'
.$smd_tags_styles['common']
.'</style>';
// Prefs check
$prefset = smd_tags_pref_get(array_keys($smd_tag_prefs[0]));
$numReqPrefs = count($smd_tag_prefs[0]);
$numPrefs = count($prefset);
$adv_text = gTxt('smd_tag_extras');
echo script_js(<<<EOJS
// Concatenate checkbox options for storage
function smd_presub() {
var smd_out = "";
jQuery("#smd_tags_enable_holder :checkbox").each(function() {
smd_out += (this.checked) ? 1 : 0;
});
jQuery("#smd_tag_p_enable").val(smd_out);
return true;
}
jQuery(function() {
jQuery('.smd_tag_btn_advanced_toggle').click(function(e) {
e.preventDefault();
jQuery('.smd_tag_btn_advanced').toggle('fast');
});
});
EOJS
);
if (smd_tags_table_exist(1)) {
// Tables installed
if ($numPrefs == $numReqPrefs) {
// Prefs all installed
echo hed(gTxt('smd_tag_prefs_title').sp.$btnHelp, 1);
echo '<div class="txp-control-panel">'
.graf(
$btnSync
.n.$btnManage
.n.tag($btnRemove.' '.$btnRemoveTbl.' '.$btnRebuildTbl, 'span', ' class="smd_tag_btn_advanced smd_hidden"')
, ' class="txp-buttons"')
.n.href($adv_text, '#', ' class="smd_tag_btn_advanced_toggle"')
.'</div>';
echo '<form method="post" action="?event=smd_tags' . a . 'step=smd_tags_prefs_update" onsubmit="return smd_presub();">';
$ctr = 1;
echo startTable('', '', 'txp-list');
foreach ($prefset as $pnam => $parr) {
$pval = $parr['val'];
echo (isset($smd_tag_prefs[2][$ctr])) ? tr(tdcs(hed($smd_tag_prefs[2][$ctr], 3, ' class="pref-heading"'), 4)) : '';
$label = '<label for="'.$pnam.'">'.gTxt($pnam).'</label>';
$out = tda($label, ' class="pref-label"');
switch($pnam) {
case "smd_tag_p_enable":
$options = smd_tags_pref_explode($pval);
$out .= '<td id="smd_tags_enable_holder" class="pref-value">';
foreach ($options as $opt => $onoff) {
$out .= '<label>'.$opt.'</label>' . checkbox("enable".$opt, 1, $onoff) . '  ';
}
$out .= '</td>';
$out .= fInput('hidden', 'smd_tag_p_enable', join($options),'','','','','','smd_tag_p_enable');
break;
case "smd_tag_p_size":
case "smd_tag_t_size_desc":
case "smd_tag_t_cols":
case "smd_tag_t_hilite":
case "smd_tag_t_hover":
case "smd_tag_t_indent":
case "smd_tag_u_pnam":
case "smd_tag_u_ptyp":
case "smd_tag_u_sec":
case "smd_tag_p_master":
case "smd_tag_u_combi_and":
case "smd_tag_u_combi_or":
$out .= tag(fInput('text', $pnam, $pval, 'edit', '', '', 20, '', $pnam), 'td', ' class="pref-value"');
break;
case "smd_tag_t_mdelim":
$out .= tag(fInput('text', $pnam, $pval, 'edit', '', '', 1, '', $pnam), 'td', ' class="pref-value"');
break;
case "smd_tag_p_qtpath":
case "smd_tag_p_qtstyl":
$out .= tag(fInput('text', $pnam, $pval, 'edit', '', '', 30, '', $pnam), 'td', ' class="pref-value"');
break;
case "smd_tag_t_auto":
$out .= tda(onoffRadio($pnam, $pval), ' class="pref-value"');
break;
case "smd_tag_t_colsord":
$out .= tda(radio($pnam,0,(($pval==0)?1:0)).gTxt('smd_tag_by_column')." " . radio($pnam,1,(($pval==1)?1:0)).gTxt('smd_tag_by_row')." ", ' class="pref-value"');
break;
case "smd_tag_t_deltree":
$out .= tda(radio($pnam,0,(($pval==0)?1:0)).gTxt('smd_tag_children_pro')." " . radio($pnam,1,(($pval==1)?1:0)).gTxt('smd_tag_children_del')." ", ' class="pref-value"');
break;
case "smd_tag_t_count":
case "smd_tag_t_delused":
case "smd_tag_t_desc_textile":
case "smd_tag_t_desc_tooltip":
case "smd_tag_t_enrep":
case "smd_tag_p_linkcat":
case "smd_tag_p_listpar":
case "smd_tag_u_combi":
$out .= tda(yesnoRadio($pnam, $pval), ' class="pref-value"');
break;
case "smd_tag_t_astart":
$out .= tda(radio($pnam,0,(($pval==0)?1:0)).gTxt('smd_tag_prefs_pane')." " . radio($pnam,1,(($pval==1)?1:0)).gTxt('smd_tag_tags_pane')." ", ' class="pref-value"');
break;
case "smd_tag_p_qtag":
$out .= tda(radio($pnam,0,(($pval==0)?1:0)).gTxt('off')." " . radio($pnam,1,(($pval==1)?1:0)).gTxt('smd_tag_ac_std')." "." " . radio($pnam,2,(($pval==2)?1:0)).gTxt('smd_tag_ac_str')." ", ' class="pref-value"');
break;
case "smd_tag_p_input":
$out .= tda(radio($pnam,0,(($pval==0)?1:0)).gTxt('smd_tag_sel_list')." ". radio($pnam,1,(($pval==1)?1:0)).gTxt('smd_tag_textlist')." ". radio($pnam,2,(($pval==2)?1:0)).gTxt('smd_tag_textbox')." ". radio($pnam,3,(($pval==3)?1:0)).gTxt('smd_tag_textboxplus')." ", ' class="pref-value"');
break;
case "smd_tag_p_lbi":
$out .= tda(radio($pnam,0,(($pval==0)?1:0)).gTxt('no')." ". radio($pnam,1,(($pval==1)?1:0)).gTxt('smd_tag_bi_up')." ". radio($pnam,2,(($pval==2)?1:0)).gTxt('smd_tag_bi_down')." ". radio($pnam,3,(($pval==3)?1:0)).gTxt('smd_tag_bi_both')." ", ' class="pref-value"');
break;
}
echo tr($out);
$ctr++;
}
echo tr(tda($btnPrefsSave, $btnStyle));
echo endTable()
.tInput()
.'</form>';
} else if ($numPrefs > 0 && $numPrefs < $numReqPrefs) {
// Prefs possibly corrupt, or plugin updated
echo startTable()
.tr(tda(strong(gTxt('smd_tag_prefs_some')).br.br
.gTxt('smd_tag_prefs_some_explain').br.br
.gTxt('smd_tag_prefs_some_opts1'), ' colspan="2"')
)
.tr(
tda($btnRemove,$btnStyle)
. tda($btnInstall, $btnStyle)
)
.endTable();
} else {
// Prefs not installed
echo startTable()
.tr(tda(gTxt('smd_tag_prefs_not_installed'), ' colspan="2"'))
.tr(tda($btnInstall, $btnStyle))
.endTable();
}
} else {
// Tables not installed
echo startTable()
.tr(tda(strong(gTxt('smd_tag_prefs_some_tbl')).br.br
.gTxt('smd_tag_prefs_some_explain').br.br
.gTxt('smd_tag_prefs_some_opts2'), ' colspan="2"')
)
.tr(tda($btnInstallTbl, $btnStyle))
.endTable();
}
}
// ------------------------
// Manage tags in a similar way to the categories tab
function smd_tags_manage($message='', $report='') {
global $smd_tag_prefs, $smd_tags_styles;
extract(doSlash(gpsa(array('smd_tag_name', 'smd_tag_title', 'smd_tag_description', 'smd_tag_parent', 'smd_tag_cat', 'smd_tag_type', 'smd_tag_id'))));
pagetop(gTxt('smd_tag_manage_lbl'),$message);
extract(smd_tags_prefs_buttons());
// Prefs check
$prefset = smd_tags_pref_get(array_keys($smd_tag_prefs[0]));
$numReqPrefs = count($smd_tag_prefs[0]);
$numPrefs = count($prefset);
$stylereps = array(
'{hov}' => $prefset['smd_tag_t_hover']['val'],
'{cur}' => $prefset['smd_tag_t_hilite']['val'],
);
$types = array(
'article' => ucfirst(gtxt('article')),
'image' => gtxt('tag_image'),
'file' => gtxt('file'),
'link' => gtxt('tag_link'),
);
// Make up the radio buttons
$smd_tag_type = (!empty($smd_tag_type)) ? $smd_tag_type : 'article';
$radios = array();
foreach ($types as $key => $val) {
$id = 'smd_tags_type-'.$key;
$radios[] = n.t.radio('smd_tags_type', $key, ($smd_tag_type == $key) ? 1 : 0, $id).
'<label for="'.$id.'">'.$val.'</label>';
}
$radios = join(sp.sp, $radios).n;
// Create all the lists but hide them with CSS. jQuery takes over and switches between them on demand
if (smd_tags_table_exist(1) && $numPrefs == $numReqPrefs) {
// Inject styles
echo '<style type="text/css">'
.strtr($smd_tags_styles['tag_manager'], $stylereps)
.$smd_tags_styles['report'].$smd_tags_styles['common']
.'</style>';
// Sanitize the multi-tag delimiter
$mdelim = substr($prefset['smd_tag_t_mdelim']['val'], 0, 1);
$mdelim = ($mdelim == '') ? '' : '\\'.$mdelim;
$colOpts = do_list($prefset['smd_tag_t_cols']['val'], ':');
$layout = ($colOpts[0] == '0' || $colOpts[0] == 'list') ? 'list' : (($colOpts[0] == 'group') ? 'listgrp' : 'table');
$colOrder = ($prefset['smd_tag_t_colsord']['val'] == 0) ? 'bycol' : 'byrow';
$wraptag = $layout.':';
if ($layout == 'table') {
$tagCols = (is_numeric($colOpts[0]) && $colOpts[0] > 0) ? $colOpts[0] : 1;
$wraptag .= $tagCols;
$sel = 'td';
} else {
$wraptag .= (isset($colOpts[1])) ? $colOpts[1] : '0';
$sel = 'li';
}
$wraptag .= ':cols:'.$colOrder;
$counts = $prefset['smd_tag_t_count']['val'];
$clink = $prefset['smd_tag_p_linkcat']['val'];
$showrep = $report && $prefset['smd_tag_t_enrep']['val'];
foreach (array_keys($types) as $type) {
$divtagid = "smd_tags_grp_".$type;
$taglist = "tags_".$type;
$$taglist = smd_tag_list_adm(
array(
"type" => $type,
"wraptag" => $wraptag,
"count" => $counts,
"indent" => $prefset['smd_tag_t_indent']['val']
)
);
//TOFIX: Find a way to add the divtagid only when needed. At the moment it appears twice on the page (illegal DOM)
$tagout[$type] = '<div id="'.$divtagid.'">'.($$taglist ? $$taglist : gTxt('smd_tag_no_tags', array('{type}' => $type))).'</div>';
echo '<div class="smd_hidden">' . $tagout[$type] . '</div>';
}
$qs = array(
"event" => "smd_tags",
);
$qsVars = "index.php".join_qs($qs);
$count_lbl = gTxt('smd_tag_count_lbl');
echo script_js(<<<EOJS
var smd_tags_step = '';
// Page initialisation
jQuery(function() {
// Indicate parent list is loading
jQuery(".smd_tags_showpars").ajaxStart(function() {
jQuery(this).fadeTo("normal", 0.33);
}).ajaxStop(function() {
jQuery(this).fadeTo("normal", 1);
});
// Switch between types
jQuery("input[name='smd_tags_type']").change(function () {
var smd_newcontent = jQuery("#smd_tags_grp_"+jQuery(this).val()).html();
jQuery("input[name='smd_tag_id']").val(''); // Changing type forces any 'save' to trigger the 'create' behaviour
jQuery(".smd_tags_showlist").html(smd_newcontent);
var nam = jQuery("input[name='smd_tag_oname']").val();
var dsc = jQuery("input[name='smd_tag_description']").val();
var tid = jQuery("input[name='smd_tag_id']").val();
var typ = jQuery("input[name='smd_tags_type']:checked").val();
var par = 'root';
var cat = '';
jQuery(".smd_tags_showlist {$sel} span[name='smd_tagnam']").each(function() {
if (jQuery(this).text() == nam) {
jQuery(this).parent().addClass('smd_current');
par = jQuery(this).siblings(":eq(2)").text();
cat = jQuery(this).siblings(":eq(3)").text();
dsc = jQuery(this).siblings(":eq(4)").text();
tid = (tid == '') ? jQuery(this).siblings(":eq(0)").text() : tid;
} else {
jQuery(this).parent().removeClass('smd_current');
}
});
// Re-insert the ID if this named item exists in the new list
jQuery("input[name='smd_tag_id']").val(tid);
// Update the UI
smd_parentHandler(nam, typ, tid, par);
smd_catHandler(cat, typ);
jQuery(".smd_tags_linkcat select option[value='"+cat+"']").prop("selected", true);
smd_autofocus();
smd_cellHandler();
jQuery("input[name='smd_tags_type']").each(function () {
if (jQuery(this).val() == typ) {
jQuery(this).next().addClass('smd_current');
} else {
jQuery(this).next().removeClass('smd_current');
}
});
});
// Call the onchange handler for the current type
jQuery("input[name='smd_tags_type']:checked").change();
// Autotag - TODO: foreign character dumbdown for URI
if ({$prefset['smd_tag_t_auto']['val']}) {
spcRE = /\s/g;
badRE = /[^a-zA-Z0-9_\-{$mdelim}]/g;
jQuery("input[name='smd_tags_newtitle']").keyup(function() {
tagname = jQuery(this).val().replace(spcRE, '-').replace(badRE, '').toLowerCase();
jQuery("input[name='smd_tags_newname']").val(tagname);
});
}
// Bind Enter key to create
jQuery("input[name^='smd_tag']").keyup(function(ev) {
if (ev.keyCode == 13) {
jQuery("input[name='smd_tag_create']").click();
}
});
// Prepare the live filter
jQuery('#smd_tag_filt legend a').click(function() {
if (jQuery(this).parent().hasClass('expanded')) {
jQuery('#smd_tagfilter').focus();
}
});
jQuery('.smd_tag_list_adm {$sel}').addClass('visible');
jQuery('#smd_tagfilter').keyup(function(event) {
// if esc is pressed or nothing is entered
if (event.keyCode == 27 || jQuery(this).val() == '') {
jQuery(this).val('');
jQuery('.smd_tag_list_adm {$sel}').removeClass('visible').show().addClass('visible');
jQuery("#smd_tag_filter_count").empty();
} else {
currOpt = jQuery('#smd_tag_filt_radios :radio:checked').val();
subfilt = (currOpt == 'smd_all') ? '' : currOpt;
smd_tag_filter('.smd_tag_list_adm {$sel}', jQuery(this).val(), subfilt);
}
});
// Trigger the filter if the radio buttons are clicked
jQuery('#smd_tag_filt_radios :radio').change(function() {
jQuery('#smd_tagfilter').keyup().focus();
});
if ('{$showrep}') {
smd_tags_toggle_report();
}
});
// Keep track of which tag has been clicked so it can be edited/updated
function smd_cellHandler() {
// Cell highlights
jQuery(".smd_tags_showlist {$sel}").each(function() {
if (jQuery.trim(jQuery(this).text()) != "") {
jQuery(this).hover(
function () {
jQuery(this).addClass('smd_hover');
},
function () {
jQuery(this).removeClass('smd_hover');
}
)
.click(function() {
var nam = jQuery(this).children(":eq(0)").text();
var tid = jQuery(this).children(":eq(1)").text();
var ttl = jQuery(this).children(":eq(2)").text();
var par = jQuery(this).children(":eq(3)").text();
var cat = jQuery(this).children(":eq(4)").text();
var dsc = jQuery(this).children(":eq(5)").text();
var typ = jQuery("input[name='smd_tags_type']:checked").val()
jQuery("input[name='smd_tag_oname']").val(nam);
jQuery("input[name='smd_tags_newname']").val(nam);
jQuery("input[name='smd_tag_id']").val(tid);
jQuery("input[name='smd_tags_newtitle']").val(ttl);
jQuery("textarea[name='smd_tags_description']").val(dsc);
jQuery(".smd_tags_linkcat select option[value='"+cat+"']").prop("selected", true);
smd_parentHandler(nam, typ, tid, par);
jQuery(".smd_tags_showlist ${sel}").removeClass('smd_current');
jQuery(this).addClass('smd_current');
smd_autofocus();
});
}
});
}
// Ask Txp for the parent dropdown without child entries
function smd_parentHandler(nam, typ, tid, par) {
jQuery.post('{$qsVars}', { step: "smd_tag_parentlist", name: nam, type: typ, id: tid, link_mode: '2', _txp_token: textpattern._txp_token },
function(data) {
jQuery(".smd_tags_showpars").html(data);
if (par == "root" || par == "") {
jQuery(".smd_tags_showpars select option:first").prop("selected", true);
} else {
jQuery(".smd_tags_showpars select option[value='"+par+"']").prop("selected", true);
}
}
);
}
// Ask Txp for the category dropdown
function smd_catHandler(cat, typ) {
jQuery(".smd_tags_linkcat").fadeTo("normal", 0.33);
jQuery.post('{$qsVars}', { step: "smd_tag_catlist", name: cat, type: typ, _txp_token: textpattern._txp_token },
function(data) {
jQuery(".smd_tags_linkcat").html(data);
if (cat == "root" || cat == "") {
jQuery(".smd_tags_linkcat select option:first").prop("selected", true);
} else {
jQuery(".smd_tags_linkcat select option[value='"+cat+"']").prop("selected", true);
}
jQuery(".smd_tags_linkcat").fadeTo("normal", 1);
}
);
}
function smd_autofocus() {
jQuery("input[name='smd_tags_newtitle']").focus().select();
}
// Make sure what is in the visible boxes is POSTed via the hidden form
function smd_presub() {
jQuery("#smd_tag_postit input[name='step']").val(smd_tags_step);
jQuery("#smd_tag_postit input[name='smd_tag_name']").val(jQuery("input[name='smd_tags_newname']").val());
jQuery("#smd_tag_postit input[name='smd_tag_title']").val(jQuery("input[name='smd_tags_newtitle']").val());
jQuery("#smd_tag_postit input[name='smd_tag_type']").val(jQuery("input[name='smd_tags_type']:checked").val());
jQuery("#smd_tag_postit input[name='smd_tag_parent']").val(jQuery(".smd_tags_showpars option:selected").val());
jQuery("#smd_tag_postit input[name='smd_tag_cat']").val(jQuery(".smd_tags_linkcat option:selected").val());
jQuery("#smd_tag_postit input[name='smd_tag_description']").val(jQuery("textarea[name='smd_tags_description']").val());
jQuery("#smd_tag_postit").submit();
}
function smd_multisub() {
theType = jQuery("input[name='smd_tags_type']:checked").val();
jQuery("#smd_tag_multiform input[name='step']").val(smd_tags_step);
jQuery("#smd_tag_multiform input[name='smd_tag_type']").val(theType);
jQuery("#smd_tag_multiform input[name='smd_tag_id']").val(jQuery.map( jQuery("#smd_tags_grp_"+theType+" .visible").find("span[name='smd_tagid']"), function(n,i) {return jQuery(n).text()} ));
jQuery("#smd_tag_multiform input[name='smd_tag_name']").val(jQuery.map( jQuery("#smd_tags_grp_"+theType+" .visible").find("span[name='smd_tagnam']"), function(n,i) {return jQuery(n).text()} ));
jQuery("#smd_tag_multiform input[name='smd_tag_description']").val(jQuery.map( jQuery("#smd_tags_grp_"+theType+" .visible").find("span[name='smd_tagdsc']"), function(n,i) {return jQuery(n).text()} ));
jQuery("#smd_tag_multiform input[name='smd_tag_extra']").val(jQuery("#smd_tag_sel_secondary option:selected").val());
}
// Live tag filter
function smd_tag_filter(selector, query, nam) {
var count_lbl = '{$count_lbl}';
var query = jQuery.trim(query);
query = query.replace(/ /gi, '|'); // add OR for regex query
var re = new RegExp(query, "i");
jQuery(selector).each(function() {
sel = (typeof nam=="undefined" || nam=='') ? jQuery(this) : jQuery(this).find("span[name='"+nam+"']");
(sel.text().search(re) < 0) ? jQuery(this).hide().removeClass('visible') : jQuery(this).show().addClass('visible');
});
// Display the matched count
theType = jQuery("input[name='smd_tags_type']:checked").val();
num_matches = count_lbl + jQuery("#smd_tags_grp_"+theType+" .visible").length;
jQuery("#smd_tag_filter_count").text(num_matches);
}
// Handle secondary multi-select options
function smd_tags_seledit(obj) {
smd_tags_step = jQuery(obj).val();
theType = jQuery("input[name='smd_tags_type']:checked").val();
switch(smd_tags_step) {
case "smd_tags_multi_set_parent":
// Grab all elements of the current type that have data in them
listitems = jQuery("#smd_tags_grp_"+theType+" {$sel}").filter(function() {return jQuery(this).find('span').size() > 0}).get();
listitems.sort(function(a, b) {
var compA = jQuery(a).text().toUpperCase();
var compB = jQuery(b).text().toUpperCase();
return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;
});
out = '<select id="smd_tag_sel_secondary">';
out += '<option value="0"></option>';
jQuery.each(listitems, function() {
item = jQuery(this);
tagid = jQuery(this).find("span[name='smd_tagid']").text();
tagnam = jQuery(this).find("span[name='smd_tagnam']").text();
out += '<option value="'+tagid+'">'+tagnam+'</option>';
});
out += '</select>';
jQuery("#smd_tag_multi_placeholder").html(out);
break
case "smd_tags_multi_catlink":
jQuery.post('{$qsVars}', { step: "smd_tag_catlist", type: theType, html_id: 'smd_tag_sel_secondary', _txp_token: textpattern._txp_token },
function(data) {
jQuery("#smd_tag_multi_placeholder").html(data);
}
);
break
case "smd_tags_delete":
case '':
jQuery("#smd_tag_multi_placeholder").empty();
break;
}
}
function smd_tags_toggle_report() {
jQuery("#smd_tag_report_pane").toggle('normal');
}
EOJS
);
}
// The tag management panel
if (smd_tags_table_exist()) {
// Tables installed
if ($numPrefs == $numReqPrefs) {
// Prefs all installed
echo hed(gTxt('smd_tag_manage_lbl').sp.$btnHelp, 1);
echo '<div class="txp-control-panel">'
.graf(
$btnPrefs
.n.$btnSync
, ' class="txp-buttons"')
.'</div>';
// Live search / multi-edit
$filtopts = array(
'smd_all' => gTxt('smd_tag_all_lbl'),
'smd_tagttl' => gTxt('title'),
'smd_tagdsc' => gTxt('description'),
'smd_tagnam' => gTxt('name'),
'smd_tagpar' => gTxt('smd_tag_parent_lbl'),
);
$withselopts = array(
'smd_tags_delete' => gTxt('delete'),
'smd_tags_multi_set_parent' => gTxt('smd_tag_assign_parent_lbl'),
);
// Tack on the category link items if required
if ($clink) {
$filtopts['smd_tagcat'] = gTxt('smd_tag_clink_lbl');
$withselopts['smd_tags_multi_catlink'] = gTxt('smd_tag_link_to_cat_lbl');
}
$descsizes = do_list($prefset['smd_tag_t_size_desc']['val']);
$descw = (int)$descsizes[0];
$desch = (isset($descsizes[1])) ? (int)$descsizes[1] : $descw;
echo '<div class="txp-container">';
echo n. '<form id="smd_tag_multiform" action="?event=smd_tags" method="post" onsubmit="smd_multisub(); return verify(\''.gTxt('are_you_sure').'\')">'
.n. '<fieldset id="smd_tag_filt"><legend class="txp-summary lever'.(get_pref('pane_smd_tag_filters_visible') ? ' expanded' : '').'"><a href="#smd_tag_filters">'.gTxt('smd_tag_tag_search').'</a></legend><div id="smd_tag_filters" class="toggle" style="display:'.(get_pref('pane_smd_tag_filters_visible') ? 'block' : 'none').'">'
.n. hInput('step', '')
.n. hInput('smd_tag_type', '')
.n. hInput('smd_tag_id', '')
.n. hInput('smd_tag_name', '')
.n. hInput('smd_tag_description', '')
.n. hInput('smd_tag_extra', '')
.n. tInput()
.n. '<input type="text" name="smd_tagfilter" value="" id="smd_tagfilter" size="30" />'
.n. '<span id="smd_tag_filter_count"></span>'
.n. '<div id="smd_tag_filt_radios">'.radioSet($filtopts, 'smd_tag_filtopt', 'smd_all', '', 'smd_tag_filtopt').'</div>'
.n. '<div id="smd_tag_multisel">'.gTxt('smd_tag_with_filtered').selectInput('smd_tag_multisel',$withselopts, '', true, ' onchange="smd_tags_seledit(this); return false;"').n.fInput('submit', '', gTxt('go'), 'publish').'</div>'
.n. '<div id="smd_tag_multi_placeholder"></div><a name="smd_tags_report_toggler" onclick="smd_tags_toggle_report();">'.gTxt('smd_tag_recent_report').'</a>'
.n. '</div>'
.n. '</fieldset>'
.n. '</form>';
// Report popup
// TODO: jQuery UI in 4.6
echo n. '<div id="smd_tag_report_pane"><div><input type="button" class="publish smd_tags_btn_ok" value="'.gTxt('ok').'" onclick="smd_tags_toggle_report();" /><h2>'.gTxt('smd_tag_report_lbl').'</h2>'.$report.'</div></div>'
.n. '<form method="post" id="smd_tag_postit" action="?event=smd_tags">'
.n. hInput('step', '')
.n. hInput('smd_tag_id', $smd_tag_id)
.n. hInput('smd_tag_name', '')
.n. hInput('smd_tag_oname', $smd_tag_name)
.n. hInput('smd_tag_parent', '')
.n. hInput('smd_tag_cat', '')
.n. hInput('smd_tag_title', '')
.n. hInput('smd_tag_type', '')
.n. hInput('smd_tag_description', '')
.n. tInput()
.n. '</form>';
// Tag create/edit row
echo n.hed(gTxt('smd_tag_create_lbl'), 2)
.n.'<div class="smd_tags_new">'
.n.'<div class="smd_tags_input_group">'
. '<label for="smd_tags_newtitle">'.gTxt('title').'</label>'
. fInput('text', 'smd_tags_newtitle', $smd_tag_title, '', '', '', '', '', 'smd_tags_newtitle')
. '<label for="smd_tags_newname">'.gTxt('name').'</label>'
. fInput('text', 'smd_tags_newname', $smd_tag_name, '', '', '', '', '', 'smd_tags_newname')
.n.'</div>'
.n.'<div class="smd_tags_input_group">'
. '<label for="smd_tags_description">'.gTxt('description').'</label>'
. text_area('smd_tags_description', $desch, $descw, $smd_tag_description, 'smd_tags_description')
.n.'</div>'
.n.'<div class="smd_tags_input_group">'
. '<label>'.gTxt('parent').'</label>'
. '<span class="smd_tags_showpars"></span>'
. (($clink)
? n. '<label>'.gTxt('smd_tag_clink_lbl').'</label>'
.n. '<span class="smd_tags_linkcat"></span>'
: ''
)
.n.'</div>'
.n.'<div class="smd_tags_input_group">'
.n. $btnCreate
.n. $btnSave
.n. $btnDelete
.n.'</div>'
.n.'</div>';
// The tags themselves
echo '<div class="smd_tags_pool">'
.n. hed(gTxt('smd_tag_pool_lbl'), 2)
.n. '<div class="smd_tags_type_selection">' . $radios .n. '</div>'
.n. '<div class="smd_tags_showlist">' . $tagout[$smd_tag_type] .n. '</div>'
.n.'</div>'
.n.'</div>';
} else if ($numPrefs > 0 && $numPrefs < $numReqPrefs) {
// Prefs possibly corrupt, or plugin updated
echo n. startTable()
.n. tr(
tda(strong(gTxt('smd_tag_prefs_some')).br.br
.gTxt('smd_tag_prefs_some_explain').br.br
.gTxt('smd_tag_prefs_some_opts1'), ' colspan="2"')
)
.n. tr(
tda($btnRemove,$btnStyle)
. tda($btnInstall, $btnStyle)
)
.n. endTable();
} else {
// Prefs not installed
echo n. startTable()
.n. tr(tda(gTxt('smd_tag_prefs_not_installed'), ' colspan="2"'))
.n. tr(tda($btnInstall, $btnStyle))
.n. endTable();
}
} else {
// Tables not installed
echo n. startTable()
.n. tr(tda(strong(gTxt('smd_tag_prefs_some_tbl')).br.br
.gTxt('smd_tag_prefs_some_explain').br.br
.gTxt('smd_tag_prefs_some_opts2'), ' colspan="2"')
)
.n. tr(tda($btnInstallTbl, $btnStyle))
.n. endTable();
}
}
// ------------------------
// Store the tag that is currently being edited/created
function smd_tag_save() {
// Defer doSlash of description until after Textile's had a go
extract(doSlash(gpsa(array('smd_tag_oname', 'smd_tag_name', 'smd_tag_title', 'smd_tag_parent', 'smd_tag_cat', 'smd_tag_type', 'smd_tag_id'))));
@include_once txpath.'/publish.php'; // for parse()
@include_once txpath.'/lib/classTextile.php';
$smd_tag_description = $smd_tags_desctile = ps('smd_tag_description');
$ctrls = smd_tags_pref_get(array('smd_tag_t_mdelim', 'smd_tag_t_desc_textile'), 1);
$mdelim = $ctrls['smd_tag_t_mdelim']['val'];
$txt_desc = $ctrls['smd_tag_t_desc_textile']['val'];
$message = $report = '';
$missing = 0;
$ok = $notok = array();
$smd_tag_parent = (!empty($smd_tag_parent)) ? $smd_tag_parent : 'root';
$smd_tag_name = trim($smd_tag_name);
if (class_exists('Textile')) {
$textile = new Textile();
$smd_tags_desctile = doSlash((($txt_desc) ? $textile->TextileThis(parse($smd_tag_description)) : parse($smd_tag_description)));
}
$smd_tag_description = doSlash($smd_tag_description);
// Can't use safe_upsert() because the WHERE is for ID AND type
if (empty($smd_tag_id)) {
// Create
if ($smd_tag_name=='' && $smd_tag_title=='') {
$message = array(gTxt('smd_tag_no_name', array('{type}' => ucfirst($smd_tag_type))), E_WARNING);
} else {
$alltagnam = do_list($smd_tag_name, $mdelim);
$alltagttl = do_list($smd_tag_title, $mdelim);
$numtags = count($alltagnam);
foreach ($alltagnam as $idx => $theTag) {
if ($theTag == '') {
$missing++;
}
$theTtl = (isset($alltagttl[$idx]) && $alltagttl[$idx] != '') ? $alltagttl[$idx] : $theTag;
$theNam = ($theTag == '') ? sanitizeForUrl($theTtl) : sanitizeForUrl($theTag);
$exists = safe_field('name', SMD_TAG, "name = '$theNam' AND type = '$smd_tag_type'");
$parex = safe_field('name', SMD_TAG, "name = '$smd_tag_parent' AND type = '$smd_tag_type'");
if ($exists) {
if ($numtags > 1) {
$notok[$idx] = 'smd_tag_already_exists';
} else {
$message = array(gTxt('smd_tag_exists', array('{name}' => $theNam, '{type}' => ucfirst($smd_tag_type))), E_WARNING);
}
} else {
$smd_tag_id = safe_insert(
SMD_TAG,
"name='$theNam', title='$theTtl', description='$smd_tag_description', desc_html='$smd_tags_desctile', parent='".(($parex) ? $smd_tag_parent : 'root')."', type='$smd_tag_type'"
);
if ($smd_tag_id > 0) {
if ($smd_tag_cat) {
safe_insert(SMD_TAGC,"tag_id='$smd_tag_id', cat_id=(SELECT id FROM ".PFX."txp_category WHERE name='$smd_tag_cat' AND type='$smd_tag_type')");
}
if ($numtags > 1) {
$ok[] = $theNam;
} else {
$message = gTxt('smd_tag_created', array('{name}' => $theNam, '{type}' => ucfirst($smd_tag_type)));
}
} else {
if ($numtags > 1) {
$notok[$idx] = 'smd_tag_unable_to_create';
} else {
$message = array(gTxt('smd_tag_not_created', array('{name}' => $smd_tag_name, '{type}' => ucfirst($smd_tag_type))), E_WARNING);
}
}
}
}
// Generate the report
if ($ok) {
$report = gTxt('smd_tag_created_rep_lbl').join(', ', $ok).'.';
}
if ($notok) {
$msgs = array();
foreach ($notok as $idx => $reason) {
$msgs[] = $alltagnam[$idx].sp.'('.gTxt($reason).')';
}
$report .= br.br.gTxt('smd_tag_not_created_rep_lbl').join(', ', $msgs);
$report .= ($missing) ? br.br.gTxt('smd_tag_missing_rep_lbl').$missing : '';
}
}
} else {
// Update - no need to maintain referential integrity unlike txp_cats
// since tags are stored against item IDs
$smd_tag_title = (empty($smd_tag_title)) ? $smd_tag_name : $smd_tag_title;
$smd_tag_name = (empty($smd_tag_name)) ? sanitizeForUrl($smd_tag_title) : sanitizeForUrl($smd_tag_name);
$existing_id = safe_field('id', SMD_TAG, "name = '$smd_tag_name' and type = '$smd_tag_type'");
if ($existing_id and $existing_id != $smd_tag_id) {
$message = array(gTxt('smd_tag_exists', array('{name}' => $smd_tag_name, '{type}' => ucfirst($smd_tag_type))), E_WARNING);
} else {
if (safe_update(
SMD_TAG,
"name='$smd_tag_name', title='$smd_tag_title', parent='$smd_tag_parent', description='$smd_tag_description', desc_html='$smd_tags_desctile'",
"type='$smd_tag_type' AND id=$smd_tag_id"
)) {
safe_update(SMD_TAG, "parent='$smd_tag_name'", "parent='$smd_tag_oname' AND type='$smd_tag_type'");
if ($smd_tag_cat) {
safe_upsert(SMD_TAGC, "cat_id=(SELECT id FROM ".PFX."txp_category WHERE name='$smd_tag_cat' AND type='$smd_tag_type')", "tag_id = '$smd_tag_id'");
} else {
safe_delete(SMD_TAGC, "tag_id=$smd_tag_id");
}
$message = gTxt('smd_tag_updated', array('{name}' => $smd_tag_name, '{type}' => ucfirst($smd_tag_type)));
} else {
$message = array(gTxt('smd_tag_not_updated', array('{name}' => $smd_tag_name, '{type}' => ucfirst($smd_tag_type))), E_WARNING);
}
}
}
rebuild_tree_full($smd_tag_type, SMD_TAG);
// Force smd_tag_id to the new ID so it can be immediately edited
$_POST['smd_tag_id'] = $smd_tag_id;
smd_tags_manage($message, $report);
}
// ------------------------
// Store new tag - uses smd_tag_save without the ID/old_name
function smd_tag_create() {
// extract(doSlash(gpsa(array('smd_tag_name', 'smd_tag_title', 'smd_tag_description', 'smd_tag_parent', 'smd_tag_type'))));
unset($_POST['smd_tag_id']);
smd_tag_save();
}
// ------------------------
// Check if the passed tag exists.
// Return its ID if it does or insert it if it doesn't. Optionally assign it to a parent tag / Txp category
function smd_tag_getsert($tag_name, $tag_type, $tag_title='', $tag_parent='', $tag_cat='', $tag_desc='', $force_parent=false, $force_cat=false) {
$ctrls = smd_tags_pref_get(array('smd_tag_p_linkcat'), 1);
$clink = $ctrls['smd_tag_p_linkcat']['val'];
$tag_title = ($tag_title == '') ? $tag_name : $tag_title;
$tag_title = doSlash($tag_title);
$tag_name = doSlash(sanitizeForUrl($tag_name));
$tag_type = doSlash($tag_type);
$tag_desc = doSlash($tag_desc);
$tag_parent = ($tag_parent) ? doSlash(sanitizeForUrl($tag_parent)) : 'root';
$tag_cat = doSlash($tag_cat);
$ret = safe_field('id', SMD_TAG, "name = '$tag_name' AND type = '$tag_type'");
if (!$ret) {
$ret = safe_insert(SMD_TAG, "name='".$tag_name."', title='$tag_title', parent='$tag_parent', type='$tag_type', description='$tag_desc', desc_html='$tag_desc'");
rebuild_tree_full($tag_type, SMD_TAG);
} elseif($force_parent) {
$upd = safe_update(SMD_TAG, "parent='$tag_parent'", "id='".doSlash($ret)."'");
rebuild_tree_full($tag_type, SMD_TAG);
}
// Assign tag to category if required
$ret = doSlash($ret);
if ($clink && $tag_cat) {
$catid = safe_field('cat_id', SMD_TAGC, "tag_id='$ret'");
if ($catid && $force_cat) {
safe_delete(SMD_TAGC, "cat_id='".$catid."' AND tag_id='".$ret."'");
$catid = false;
}
if(!$catid) {
safe_insert(SMD_TAGC, "tag_id='$ret', cat_id=(SELECT id FROM ".PFX."txp_category WHERE name='$tag_cat' AND type='$tag_type')");
}
}
return $ret;
}
// ------------------------
// Link the passed tags array with the given item ID
function smd_tag_link($itemid, $tagarr, $tag_type) {
assert_int($itemid);
// TODO: Remove any existing tags?? Perhaps make this an option?
// safe_delete(SMD_TAGU, 'item_id="'.$itemid.'" AND type="'.doSlash($tag_type).'"');
$tag_type = doSlash($tag_type);
$ctr = $ret = 0;
foreach ($tagarr as $theTag) {
if ($theTag == '') continue;
$theTag = doSlash($theTag); // Probably not necessary
$exists = safe_field('item_id', SMD_TAGU, "tag_id = '$theTag' AND item_id = '$itemid' AND type = '$tag_type'");
if (!$exists) {
$ret = safe_insert(SMD_TAGU, 'tag_id = "'.$theTag.'", item_id="'.$itemid.'", type="'.$tag_type.'"');
if ($ret) {
$ctr++;
}
}
}
return $ctr;
}
// ------------------------
// Delete the given tag(s). Respects noclobber/tree/orphans depending on prefs
function smd_tags_delete() {
global $smd_tag_prefs;
extract(doSlash(gpsa(array('smd_tag_id', 'smd_tag_name', 'smd_tag_type'))));
$message = $msgExtra = $report = '';
$ok = $notok = array();
$pref = smd_tags_pref_get(array('smd_tag_t_delused', 'smd_tag_t_deltree'));
$delu = $pref['smd_tag_t_delused']['val'];
$delt = $pref['smd_tag_t_deltree']['val'];
$smd_tag_id = do_list($smd_tag_id);
$smd_tag_name = do_list($smd_tag_name);
$with_child = safe_column('id', SMD_TAG, "id IN ('".join("','", $smd_tag_id)."') AND (((rgt-lft-1)/2) > 0) AND type='$smd_tag_type'");
$used_tags = safe_column('tag_id', SMD_TAGU, "tag_id IN ('".join("','", $smd_tag_id)."') AND type='$smd_tag_type'");
$to_del = $smd_tag_id;
// Protect used and child tags if required
if ($delu == 0) {
$to_del = array_diff($smd_tag_id, $with_child, $used_tags);
foreach (array_merge($with_child, $used_tags) as $delid) {
$notok[] = $smd_tag_name[array_search($delid, $smd_tag_id)];
}
}
$orphans = array();
if ($delt) {
// Add child tags if they are to be deleted
$ids = $to_del;
foreach ($to_del as $delid) {
$row = safe_row('*', SMD_TAG, "id = '$delid' AND type='$smd_tag_type'");
$ids = array_merge($ids, safe_column('id', SMD_TAG, "type = '$smd_tag_type' AND (lft BETWEEN ".$row['lft']." AND ".$row['rgt'].")"));
}
$to_del = array_unique($ids);
$msgExtra = gTxt('smd_tag_children_and');
} else {
// Collect the potential orphans and prepare to promote them
foreach ($to_del as $delid) {
$children = safe_column('id', SMD_TAG, "parent = '".$smd_tag_name[array_search($delid, $smd_tag_id)]."' AND type='$smd_tag_type'");
$children = array_diff($children, $to_del);
if ($children) {
$orphans += $children;
}
}
}
// Everything's prepped: begin the deletion
$numdel = count($to_del);
// Make a note of what we're about to do for the report
$all_dels = safe_rows('id, name', SMD_TAG, "id IN (" . join(',', quote_list($to_del)) . ")");
$all_names = array();
foreach ($all_dels as $aname) {
$all_names[$aname['id']] = $aname['name'];
}
foreach ($to_del as $delid) {
if (isset($all_names[$delid])) {
$ok[] = $all_names[$delid];
}
}
// First get rid of the direct tags
$where = " IN ('".join("','", $to_del)."')";
$ret = safe_delete(SMD_TAG, 'id'.$where." AND type='$smd_tag_type'");
if ($ret) {
if (!$delt) {
// Promote the orphans if required
$orphlist = safe_column('name', SMD_TAG, "type='$smd_tag_type' AND id IN ('".join("','", $orphans)."')");
safe_update(SMD_TAG, "parent='root'", "type='$smd_tag_type' AND id IN ('".join("','", $orphans)."')");
}
// Remove references to any deleted tags in articles and linked categories
safe_delete(SMD_TAGU, 'tag_id'.$where." AND type='$smd_tag_type'");
safe_delete(SMD_TAGC, 'tag_id'.$where);
// Force all smd_tag_ boxes clear in the form
$_POST['smd_tag_id'] = $_POST['smd_tag_oname'] = $_POST['smd_tag_name'] = $_POST['smd_tag_title'] = $_POST['smd_tag_parent'] = '';
}
// Build and generate the final report
if ($numdel > 1) {
$message = gTxt('smd_tag_multi_delete', array('{number}' => $numdel));
if ($ok) {
$report = gTxt('smd_tag_deleted_lbl').join(', ', $ok).'.';
}
if ($notok) {
$report .= br.br.gTxt('smd_tag_err_in_use_lbl').join(', ', $notok);
}
if ($orphans) {
$report .= br.br.gTxt('smd_tag_err_orphaned_lbl').join(', ', $orphlist);
}
} else {
if ($ok) {
$message = gTxt('smd_tag_deleted', array('{type}' => ucfirst($smd_tag_type), '{name}' => join(', ', $ok))).$msgExtra;
} else if ($notok) {
$message = gTxt('smd_tag_in_use', array('{type}' => ucfirst($smd_tag_type), '{name}' => join(', ', $notok)));
}
}
rebuild_tree_full($smd_tag_type, SMD_TAG);
smd_tags_manage($message, $report);
}
// ------------------------
// Make the passed tag a child of the given parent (if possible)
function smd_tag_assign_parent($parid, $childid, $tag_type, $childnam, $rebuild = true) {
assert_int($childid);
assert_int($parid);
$status = array();
if ($childid == $parid) {
$status[] = 'err_self_parent';
} else {
$exists = safe_field('name', SMD_TAG, "id = '".doSlash($parid)."' AND type = '".doSlash($tag_type)."'");
$exists = ($exists == '') ? 'root' : $exists;
$ret = safe_update(SMD_TAG, "parent='".doSlash($exists)."'", "id='".$childid."'");
if ($rebuild) {
// Doesn't hurt to rebuild it anyway, even if the assignment fails
rebuild_tree_full($tag_type, SMD_TAG);
}
if ($ret) {
$status[] = 'parent_linked';
} else {
$status[] = 'parent_not_linked';
}
}
return $status;
}
// ------------------------
// Assign a parent to a bunch of tags at once
// Cheating code really; just calls the smd_tag_assign_parent for each tag but delays the
// tree rebuild until the end for speed
function smd_tags_multi_set_parent() {
global $smd_tag_prefs;
extract(doSlash(gpsa(array('smd_tag_type', 'smd_tag_id', 'smd_tag_name', 'smd_tag_extra'))));
$message = $report = '';
$ok = $notok = array();
$theList = array_combine(do_list($smd_tag_id), do_list($smd_tag_name));
foreach ($theList as $idx => $name) {
$status = smd_tag_assign_parent($smd_tag_extra, $idx, $smd_tag_type, $name, false);
if (in_array('parent_linked', $status)) {
$ok[] = $name;
} else {
if (($pos = array_search('parent_not_linked', $status)) !== false) {
unset($status[$pos]);
}
$notok[$idx] = $status;
}
}
if ($ok) {
$parnam = safe_field('name', SMD_TAG, "id='".doSlash($smd_tag_extra)."'");
$parnam = ($parnam == '') ? 'root' : $parnam ;
$report = gTxt('smd_tag_parent_linked_lbl', array('{parent}' => $parnam)).join(', ', $ok).'.';
}
if ($notok) {
$msgs = array();
foreach ($notok as $idx => $reasons) {
$rsn = array();
foreach ($reasons as $reason) {
$rsn[] = gTxt($reason);
}
$msgs[] = $theList[$idx].sp.'('.join('/', $rsn).')';
}
$report .= br.br.gTxt('smd_tag_not_parent_linked_lbl').join(', ', $msgs);
}
// Rebuild the tree now all the manipulation has been done
rebuild_tree_full($smd_tag_type, SMD_TAG);
smd_tags_manage($message, $report);
}
// ------------------------
// Links the passed tag with the given category
function smd_tag_assign_category($parnam, $childid, $cat_type, $childnam) {
assert_int($childid);
$status = array();
// Unlink if parent category name is empty
if ($parnam == '') {
safe_delete(SMD_TAGC, "tag_id='".doSlash($childid)."'");
$status[] = 'category_unlinked';
} else {
$exists = safe_field('id', 'txp_category', "name = '".doSlash($parnam)."' AND type = '".doSlash($cat_type)."'");
if ($exists) {
safe_delete(SMD_TAGC, "tag_id='".doSlash($childid)."'");
$ret = safe_insert(SMD_TAGC, "cat_id='".doSlash($exists)."', tag_id = '".doSlash($childid)."'");
if ($ret) {
$status[] = 'category_linked';
} else {
$status[] = 'category_unlinked';
}
}
}
return $status;
}
// ------------------------
// Assign a bunch of tags to a category
// Cheatingly calls smd_tag_assign_category multiple times
function smd_tags_multi_catlink() {
global $smd_tag_prefs;
extract(doSlash(gpsa(array('smd_tag_type', 'smd_tag_id', 'smd_tag_name', 'smd_tag_extra'))));
$message = $report = '';
$ok = $notok = array();
$theList = array_combine(do_list($smd_tag_id), do_list($smd_tag_name));
// Add in any child elements since they all need to take the category too
foreach ($theList as $idx => $name) {
$row = safe_row('*', SMD_TAG, "id='".doSlash($idx)."'");
$rs = safe_rows('id, name', SMD_TAG, "type = '".doSlash($smd_tag_type)."' AND (lft BETWEEN " .$row['lft']. " AND " .$row['rgt']. ")");
if ($rs) {
foreach ($rs as $rec) {
$theList[$rec['id']] = $rec['name'];
}
}
}
foreach ($theList as $idx => $name) {
$status = smd_tag_assign_category($smd_tag_extra, $idx, $smd_tag_type, $name);
if (in_array('category_linked', $status)) {
$ok[] = $name;
} elseif (in_array('category_unlinked', $status)) {
$notok[$idx] = $status;
}
}
if ($ok) {
$report = gTxt('smd_tag_category_linked_lbl', array('{category}' => $smd_tag_extra)).join(', ', $ok).'.';
}
if ($notok) {
$msgs = array();
foreach ($notok as $idx => $reasons) {
$msgs[] = $theList[$idx];
}
$report .= br.br.gTxt('smd_tag_category_unlinked_lbl').join(', ', $msgs);
}
smd_tags_manage($message, $report);
}
// -------------------------------------------------------------
function smd_tags_save_pane_state() {
$panes = array('smd_tag_filters');
$pane = gps('pane');
if (in_array($pane, $panes))
{
set_pref("pane_{$pane}_visible", (gps('visible') == 'true' ? '1' : '0'), 'smd_tags', PREF_HIDDEN, 'yesnoradio', 0, PREF_PRIVATE);
send_xml_response();
} else {
send_xml_response(array('http-status' => '400 Bad Request'));
}
}
// ------------------------
// Allow tags to be imported from other tagging / unlimited cats plugins, or txp fields
function smd_tags_sync($message='', $report='') {
global $smd_tag_prefs, $plugins, $smd_tags_styles;
extract(doSlash(gpsa(array('smd_tags_sync_type', 'smd_tags_sync_cfs', 'smd_tags_sync_cfs_delim', 'smd_tags_sync_section', 'smd_tags_sync_parent', 'smd_tags_delete_orig', 'smd_tags_import_tag_parent', 'smd_tags_import_cat_parent', 'smd_tags_import_force_parent', 'smd_tags_import_force_cat', 'smd_tags_do_import'))));
$ctrls = smd_tags_pref_get(array('smd_tag_p_linkcat', 'smd_tag_t_enrep'), 1);
$clink = $ctrls['smd_tag_p_linkcat']['val'];
$showrep = $report && $ctrls['smd_tag_t_enrep']['val'];
pagetop(gTxt('smd_tag_sync_lbl'),$message);
extract(smd_tags_prefs_buttons());
$plug_tt = (is_array($plugins) && in_array('tru_tags',$plugins));
$plug_uc = (is_array($plugins) && in_array('rss_unlimited_categories',$plugins));
// Prefs check
$prefset = smd_tags_pref_get(array_keys($smd_tag_prefs[0]));
$numReqPrefs = count($smd_tag_prefs[0]);
$numPrefs = count($prefset);
// Perform the import via AJAX due to the quantity of tags that may be involved
if ($smd_tags_do_import) {
$impopts = array(
'smd_tags_sync_type' => $smd_tags_sync_type,
'smd_tags_sync_cfs' => $smd_tags_sync_cfs,
'smd_tags_sync_cfs_delim' => $smd_tags_sync_cfs_delim,
'smd_tags_sync_section' => $smd_tags_sync_section,
'smd_tags_sync_parent' => $smd_tags_sync_parent,
'smd_tags_delete_orig' => $smd_tags_delete_orig,
'smd_tags_import_tag_parent' => $smd_tags_import_tag_parent,
'smd_tags_import_cat_parent' => $smd_tags_import_cat_parent,
'smd_tags_import_force_parent' => $smd_tags_import_force_parent,
'smd_tags_import_force_cat' => $smd_tags_import_force_cat,
);
smd_tags_import($impopts);
}
$qs = array(
"event" => "smd_tags",
);
$qsVars = "index.php".join_qs($qs);
// Inject styles
echo '<style type="text/css">' . $smd_tags_styles['report'] . '</style>';
echo script_js(<<<EOS
jQuery(function() {
function smd_tagRestrict(typ) {
grabcat = jQuery("#smd_tags_import_cat_parent option:selected").val();
var smd_link_mode = (('{$clink}'=='0') ? '2' : ((grabcat=='') ? '2' : ''));
if (grabcat != "undefined" || grabcat != '') {
jQuery.post('{$qsVars}', { step: "smd_tag_parentlist", name: 'smd_tag_parent', type: typ, cat: grabcat, listonly: true, html_id: 'smd_tags_import_tag_parent', link_mode: smd_link_mode, _txp_token: textpattern._txp_token },
function(data) {
jQuery("#smd_tags_import_tag_parent_holder").html(data);
jQuery("#smd_tags_import_tag_parent_holder select option[value='{$smd_tags_import_tag_parent}']").prop("selected", true);
}
);
}
}
smd_tagRestrict('article');
if ('{$clink}' == '1') {
jQuery("#smd_tags_import_cat_parent").change(function() {
smd_tagRestrict('article');
});
}
if ('{$showrep}' == '1') {
smd_tags_toggle_report();
}
jQuery('input[name="smd_tags_sync_type"]').change(function() {
if (jQuery(this + ':checked').val() == '3') {
jQuery('.smd_tags_sync_txp').show();
} else {
jQuery('.smd_tags_sync_txp').hide();
}
}).change();
});
function smd_tags_toggle_report() {
jQuery("#smd_tag_report_pane").toggle('normal');
}
EOS
);
// The tag sync preferences
if (smd_tags_table_exist()) {
// Tables installed
if ($numPrefs == $numReqPrefs) {
// Prefs all installed
echo hed(gTxt('smd_tag_sync_lbl').sp.$btnHelp, 1);
echo n. '<div class="txp-control-panel">'
.n. graf(
$btnPrefs
.n.$btnManage
, ' class="txp-buttons"')
.n. '</div>';
echo n. '<div id="smd_tag_report_pane">'
.n. '<div>'
.n. '<input type="button" class="publish smd_tags_btn_ok" value="', gTxt('ok'), '" onclick="smd_tags_toggle_report();" />'
.n. '<h2>', gTxt('smd_tag_report_lbl'), '</h2>'
.$report
.n. '</div>'
.n. '</div>'
.n. '<form method="post" id="smd_syncit" action="?event=smd_tags&step=smd_tags_sync">'
.n. startTable('', '', 'txp-list');
$lblStyle = ' class="pref-label"';
echo tr(tdcs(hed(gTxt('smd_tag_sync_plugin_opts'), 3, ' class="pref-heading"'), 4));
echo tr(
tda('<label>'.gTxt('smd_tag_sync_type_lbl').'</label>', $lblStyle)
.tda(
(($plug_tt)
? radio('smd_tags_sync_type', 0, ((empty($smd_tags_sync_type)) ? 1 : ($plug_tt ? 1 : 0) )).gTxt('smd_tag_sync_type1').' '
: ''
)
.(($plug_uc)
? radio('smd_tags_sync_type', 1, (($smd_tags_sync_type == '1') ? 1 : (($plug_uc && !$plug_tt) ? 1 : 0) )).gTxt('smd_tag_sync_type2').' '
: ''
)
. radio('smd_tags_sync_type', 2, (($smd_tags_sync_type == '2') ? 1 : ((!$plug_uc && !$plug_tt) ? 1 : 0) )).gTxt('smd_tag_sync_type3')
. radio('smd_tags_sync_type', 3, (($smd_tags_sync_type == '3') ? 1 : ((!$plug_uc && !$plug_tt && !$smd_tags_sync_type == '2') ? 1 : 0) )).gTxt('smd_tag_sync_type4')
, ' class="pref-value"')
);
// Txp category / custom_field import options
$cfs = getCustomFields();
echo ($cfs) ? tr(
tda(
'<label>'.gTxt('smd_tag_sync_cfs_lbl').'</label>', $lblStyle
)
. tag(selectInput('smd_tags_sync_cfs', $cfs, $smd_tags_sync_cfs, true), 'td', ' class="pref-value"')
, ' class="smd_tags_sync_txp"') : '';
echo ($cfs) ? tr(
tda(
'<label>'.gTxt('smd_tag_sync_cfs_delim_lbl').'</label>', $lblStyle
)
. tag(fInput('text', 'smd_tags_sync_cfs_delim', ($smd_tags_sync_cfs_delim ? $smd_tags_sync_cfs_delim : ','), 'input-xsmall', '', '', 4), 'td', ' class="pref-value"')
, ' class="smd_tags_sync_txp"') : '';
// Section import
$rs = safe_column('name', 'txp_section', "name != 'default'");
if ($rs) {
$out = tag(selectInput('smd_tags_sync_section', $rs, $smd_tags_sync_section, true), 'td', ' class="pref-value"');
} else {
$out = tag(gTxt('smd_tag_no_sections'), 'td', ' class="pref-value"');
}
echo tr(tda('<label for="smd_tags_sync_section">'.gTxt('smd_tag_sync_section_lbl').'</label>', $lblStyle) . $out);
// Start at parent category (rss_uc only)
// The list is populated anyway even if the plugin isn't active because $rsCats is used later
if ($clink) {
$rsCats = getTree('root', 'article');
if ($rsCats) {
$catlist = tag(treeSelectInput('smd_tags_sync_parent', $rsCats, $smd_tags_sync_parent, 'smd_tags_sync_parent', 35).(($plug_tt) ? gTxt('smd_tag_rss_uc_only') : ''), 'td', ' class="pref-value"');
} else {
$catlist = tag(gTxt('smd_tag_no_parent'), 'td', ' class="pref-value"');
}
echo tr(tda('<label for="smd_tags_sync_parent">'.gTxt('smd_tag_sync_parent_lbl').'</label>', $lblStyle) . $catlist);
}
// Delete original
echo tr(
tda('<label for="smd_tags_delete_orig">'.gTxt('smd_tag_sync_delete_orig_lbl').'</label>', $lblStyle)
.tda(yesnoRadio('smd_tags_delete_orig', $smd_tags_delete_orig).gTxt('smd_tag_rss_uc_tru_tags_only'), ' class="pref-value"')
);
echo tr(tdcs(hed(gTxt('smd_tag_sync_import_opts'), 3, ' class="pref-heading"'), 4));
// Assign to category
if ($clink) {
if ($rsCats) {
$catlist = tag(treeSelectInput('smd_tags_import_cat_parent', $rsCats, $smd_tags_import_cat_parent, 'smd_tags_import_cat_parent', 35), 'td', ' class="pref-value"');
} else {
$catlist = tag(gTxt('smd_tag_no_parent'), 'td', ' class="pref-value"');
}
echo tr(tda('<label for="smd_tags_import_cat_parent">'.gTxt('smd_tag_sync_parent_cat_lbl').'</label>', $lblStyle) . $catlist);
echo tr(tda('<label for="smd_tags_import_force_cat">'.gTxt('smd_tag_sync_force_cat_lbl').'</label>', $lblStyle) . td(checkbox('smd_tags_import_force_cat', 1, $smd_tags_import_force_cat)));
}
// Assign to parent tag
echo tr(tda('<label for="smd_tags_import_tag_parent">'.gTxt('smd_tag_sync_parent_tag_lbl').'</label>', $lblStyle). td('<div id="smd_tags_import_tag_parent_holder"></div>'));
echo tr(tda('<label for="smd_tags_import_force_parent">'.gTxt('smd_tag_sync_force_parent_lbl').'</label>', $lblStyle) . td(checkbox('smd_tags_import_force_parent', 1, $smd_tags_import_force_parent)));
// Report viewer
echo tr(tda('<a name="smd_tags_report_toggler" onclick="smd_tags_toggle_report();">'.gTxt('smd_tag_recent_report').'</a>').tda(gTxt('smd_tag_import_results_pt1') . '<span id="smd_tags_import_icurr"></span><span id="smd_tags_import_itot"></span> ' . gTxt('smd_tag_import_results_pt2') . '<span id="smd_tags_import_lnk_curr"></span>'));
echo tr(tda($btnSyncGo, $btnStyle));
echo endTable()
. tInput()
. '</form>';
} else if ($numPrefs > 0 && $numPrefs < $numReqPrefs) {
// Prefs possibly corrupt, or plugin updated
echo startTable()
.n. tr(tda(strong(gTxt('smd_tag_prefs_some')).br.br
.gTxt('smd_tag_prefs_some_explain').br.br
.gTxt('smd_tag_prefs_some_opts1'), ' colspan="2"')
)
.n. tr(
tda($btnRemove,$btnStyle)
. tda($btnInstall, $btnStyle)
)
.n. endTable();
} else {
// Prefs not installed
echo startTable()
.n. tr(tda(gTxt('smd_tag_prefs_not_installed'), ' colspan="2"'))
.n. tr(tda($btnInstall, $btnStyle))
.n. endTable();
}
} else {
// Tables not installed
echo startTable()
.n. tr(tda(strong(gTxt('smd_tag_prefs_some_tbl')).br.br
.gTxt('psmd_tag_refs_some_explain').br.br
.gTxt('smd_tag_prefs_some_opts2'), ' colspan="2"')
)
.n. tr(tda($btnInstallTbl, $btnStyle))
.n. endTable();
}
}
// ------------------------
// Import tags from other places
function smd_tags_import($smd_tag_options) {
extract($smd_tag_options);
$message = '';
$iparent = ($smd_tags_sync_parent) ? " AND parent = \'".doSlash($smd_tags_sync_parent)."\' " : '';
$rs = safe_rows('*', 'textpattern', (($smd_tags_sync_section) ? "Section='".doSlash($smd_tags_sync_section)."'" : '1=1'));
$row_ctr = $tag_ctr = 0;
$total = count($rs);
$ctrls = smd_tags_pref_get(array('smd_tag_t_enrep'), 1);
$showrep = $ctrls['smd_tag_t_enrep']['val'];
echo '<script type="text/javascript">';
echo "var ictr = 0; var itot = {$total}; var lnk_ctr = 0;";
echo 'jQuery(function() {';
echo 'jQuery("#smd_tags_import_itot").text("/'.$total.'");';
if ($rs) {
foreach ($rs as $row) {
switch ($smd_tags_sync_type) {
case '0':
// tru_tags
$idata = doSlash($row['Keywords']);
break;
case '1':
case '2':
// rss_uc / Txp cats
$idata = doSlash($row['ID']);
break;
case '3':
// CFs
$idata = (($smd_tags_sync_cfs && isset($row['custom_'.$smd_tags_sync_cfs])) ? doSlash($row['custom_'.$smd_tags_sync_cfs]) : '');
break;
}
$ititle = doSlash($row['Title']);
$artid = doSlash($row['ID']);
echo <<<EOJS
sendAsyncEvent(
{
event: textpattern.event,
step: 'smd_tags_import_one',
smd_tags_sync_type: '{$smd_tags_sync_type}',
smd_tags_sync_id: '{$artid}',
smd_tags_sync_data: '{$idata}',
smd_tags_sync_title: '{$ititle}',
smd_tags_sync_parent: '{$iparent}',
smd_tags_sync_cfs: '{$smd_tags_sync_cfs}',
smd_tags_sync_cfs_delim: '{$smd_tags_sync_cfs_delim}',
smd_tags_import_tag_parent: '{$smd_tags_import_tag_parent}',
smd_tags_import_cat_parent: '{$smd_tags_import_cat_parent}',
smd_tags_import_force_parent: '{$smd_tags_import_force_parent}',
smd_tags_import_force_cat: '{$smd_tags_import_force_cat}',
smd_tags_delete_orig: '{$smd_tags_delete_orig}'
}, smd_tags_group_feedback);
EOJS;
}
echo '});';
echo <<<EOJS
function smd_tags_group_feedback(data) {
ictr++;
jQuery('#smd_tags_import_icurr').text(ictr);
jQuery('#smd_tag_report_pane').append(jQuery(data).find('smd_tags_report').attr('value'));
lnk_ctr += (jQuery(data).find('smd_tags_link_ctr').attr('value')) * 1; // multiply by 1 to convert to number
jQuery('#smd_tags_import_lnk_curr').text(lnk_ctr);
if ('{$showrep}' == '1' && ictr >= itot) {
smd_tags_toggle_report();
}
}
EOJS;
echo '</script>';
}
}
// ------------------------
function smd_tags_import_one() {
$smd_tags_sync_type = gps('smd_tags_sync_type');
$smd_tags_sync_data = gps('smd_tags_sync_data');
$smd_tags_sync_id = gps('smd_tags_sync_id');
$smd_tags_sync_title = gps('smd_tags_sync_title');
$smd_tags_sync_parent = gps('smd_tags_sync_parent');
$smd_tags_sync_cfs = gps('smd_tags_sync_cfs');
$smd_tags_sync_cfs_delim = gps('smd_tags_sync_cfs_delim');
$smd_tags_import_tag_parent = gps('smd_tags_import_tag_parent');
$smd_tags_import_cat_parent = gps('smd_tags_import_cat_parent');
$smd_tags_import_force_parent = gps('smd_tags_import_force_parent');
$smd_tags_imort_force_cat = gps('smd_tags_import_force_cat');
$smd_tags_delete_orig = gps('smd_tags_delete_orig');
$tag_ids = $tag_names = $keylist = array();
if (in_array($smd_tags_sync_type, array ('0', '3'))) {
// tru_tags / Txp CF
$dlm = ($smd_tags_sync_type == '3' && $smd_tags_sync_cfs_delim) ? $smd_tags_sync_cfs_delim : ',';
$keys = do_list($smd_tags_sync_data, $dlm);
foreach ($keys as $key) {
if ($key=='') continue;
$keylist[] = array('name' => $key, 'title' => $key);
}
} else if ($smd_tags_sync_type == '1') {
// rss_uc
$clause = 'tc.article_id = '.doSlash($smd_tags_sync_data);
$keylist = getRows("SELECT c.name, c.title FROM ".PFX."textpattern_category as tc LEFT JOIN ".PFX."txp_category as c ON tc.category_id = c.id WHERE ".$clause.$smd_tags_sync_parent);
$keylist = ($keylist) ? $keylist : array();
} else if ($smd_tags_sync_type == '2') {
// txp cats
$clause = 'txt.ID = '.doSlash($smd_tags_sync_data);
$keylist = getRows("SELECT c.name, c.title FROM ".PFX."textpattern as txt LEFT JOIN ".PFX."txp_category as c ON (txt.Category1 = c.name OR txt.Category2 = c.name) WHERE ".$clause.$smd_tags_sync_parent);
$keylist = ($keylist) ? $keylist : array();
}
// Insert /update each tag in the given place
foreach ($keylist as $item) {
$itn = trim($item['name']);
$itt = trim($item['title']);
// Skip invalid entries
if ($itn == '' || $itn == NULL || $itt == '' || $itt == NULL) continue;
$id = smd_tag_getsert($itn, 'article', $itt, $smd_tags_import_tag_parent, $smd_tags_import_cat_parent, '', $smd_tags_import_force_parent, $smd_tags_import_force_cat);
if ($id) {
$tag_ids[] = $id;
$tag_names[] = $itn;
}
}
if ($tag_ids) {
$report = strong($smd_tags_sync_title).': '.join(', ', $tag_names).br;
if ($smd_tags_delete_orig) {
if ($smd_tags_sync_type == '0') {
safe_update('textpattern', "Keywords=''", "id='".doSlash($smd_tags_sync_id)."'");
} else if ($smd_tags_sync_type == '1') {
safe_delete('textpattern_category', "article_id='".doSlash($smd_tags_sync_id)."'");
} else if ($smd_tags_sync_type == '2') {
// Conspicuously missing until such time as a suitable method can be found to deal with it
} else if ($smd_tags_sync_type == '3') {
safe_update('textpattern', "custom_$smd_tags_sync_cfs=''", "id='".doSlash($smd_tags_sync_id)."'");
}
}
}
$tag_ctr = smd_tag_link($smd_tags_sync_id, $tag_ids, 'article');
send_xml_response(array('smd_tags_report' => $report, 'smd_tags_link_ctr' => $tag_ctr));
}
// ------------------------
// MIDDLEWARE - ADMIN/PUBLIC GLUE
// ------------------------
// Grab a valid tag parent list as HTML. Cannot be done easily from fixed data: AJAX is the only reliable way.
// Needs to take into account the various use cases:
// * full list (link_mode=2)
// * from given parent nodes (link_mode=1)
// * excluding forbidden items (e.g. when assigning parent to tag that is already in the subtree)
// * adding interface elements like [clr] and [tog] buttons
// TODO: take the bi-directional setting into account
function smd_tag_parentlist() {
$trycat = isset($_POST['cat']);
extract(doSlash(gpsa(array('name', 'type', 'id', 'cat', 'itemid', 'listonly', 'html_id', 'link_mode'))));
while(@ob_end_clean()); // Get rid of any page so far
$listonly = ($listonly == '' || $listonly == 'undefined') ? '' : $listonly;
$html_id = ($html_id == '' || $html_id == 'undefined') ? 'smd_tag_parent' : $html_id;
$type = ($type == '' || $type == 'undefined') ? 'article' : $type;
$cat = ($cat == '' || $cat == 'undefined') ? '' : $cat;
$link_mode = ($link_mode == '' || $link_mode == 'undefined') ? '1' : $link_mode;
$tags = ($itemid == '' || $itemid == 'undefined') ? array() : safe_rows('tag_id', SMD_TAGU, "type='$type' AND item_id='$itemid'");
$clrBtn = '[<span id="smd_clr" class="smd_fakebtn">'.gTxt('smd_tag_clear').'</span>]';
$togBtn = '[<span id="smd_tog" class="smd_fakebtn">'.gTxt('smd_tag_toggle').'</span>]';
$pref = smd_tags_pref_get(array('smd_tag_p_listpar', 'smd_tag_p_master', 'smd_tag_p_linkcat'), 1);
$incl = $pref['smd_tag_p_listpar']['val'];
$god = $pref['smd_tag_p_master']['val'];
$clink = $pref['smd_tag_p_linkcat']['val'];
$god_clause = ($god == '') ? '' : "txt.parent = '" . doSlash($god) . "'";
if ($cat && $clink) {
$cat = do_list($cat);
$rsid = array();
$rsc = getRows("SELECT DISTINCT txt.id, txt.parent, txt.lft, txt.rgt
FROM ".PFX.SMD_TAG." AS txt, ".PFX."txp_category AS txc, ".PFX.SMD_TAGC." AS txl
WHERE ( ( txt.id = txl.tag_id
AND txl.cat_id = txc.id
AND txc.name IN ('" .join("','", $cat). "') )" . ($god_clause ? ' OR '.$god_clause : '') . ")
AND txt.type = '$type'");
if ($rsc) {
foreach ($rsc as $row) {
$include_parent = ($incl || $row['parent'] == $god) ? '' : ' AND lft != '.$row['lft'];
$ids = safe_column('id', SMD_TAG, "type = '$type' AND (lft BETWEEN " .$row['lft']. " AND " .$row['rgt']. ")" . $include_parent);
$rsid = array_merge($rsid, $ids);
}
}
$cat = ($rsc) ? ' AND id IN ('.doQuote(join("','",$rsid)).')' : '';
} else if ($god != '' && $link_mode == '1') {
$ids = safe_column('id', SMD_TAG, "type = '$type' AND parent = '" . doSlash($god) . "'");
$cat = ($ids) ? ' AND id IN ('.doQuote(join("','",$ids)).')' : '';
} else {
$cat = '';
}
$link_mode = (($cat && $link_mode == '1') || $link_mode == '2') ? '1=1' : '1=0';
if ($id) {
$id = assert_int($id);
list($lft, $rgt) = array_values(safe_row('lft, rgt', SMD_TAG, 'id = '.$id));
$rs = smd_tag_tree('root', $type, "lft not between $lft and $rgt".$cat, SMD_TAG);
} else {
$rs = smd_tag_tree('root', $type, $link_mode.$cat, SMD_TAG);
}
if ($rs) {
if ($trycat && !$listonly) {
echo smd_multiTreeSelectInput($html_id, $rs, $tags).$clrBtn.$togBtn;
} else {
echo treeSelectInput($html_id, $rs, $name);
}
} else {
echo gTxt('smd_tag_no_parent');
}
exit(); // Don't call page_end()
}
// ------------------------
// Grab a valid category list
function smd_tag_catlist($cat='') {
extract(doSlash(gpsa(array('name','type','html_id'))));
while(@ob_end_clean()); // Get rid of any page so far
$type = ($type == "" || $type == "undefined") ? 'article' : $type;
$html_id = ($html_id == "" || $html_id == "undefined") ? 'smd_tags_catlist' : $html_id;
$rs = getTree('root', $type, "1=1");
if ($rs) {
echo treeSelectInput($html_id, $rs, $name, $html_id);
} else {
echo gTxt('no_categories_available');
}
exit(); // Don't call page_end()
}
// ------------------------
// Fetch a description for use as a tooltip
function smd_tag_get_desc() {
extract(doSlash(gpsa(array('tag_ref'))));
while(@ob_end_clean()); // Get rid of any page so far
$tag_ref = ($tag_ref == "" || $tag_ref == "undefined") ? '' : $tag_ref;
if ($tag_ref) {
$field = is_numeric($tag_ref) ? 'id' : 'title';
$desc = safe_field('desc_html', SMD_TAG, "$field='" . doSlash($tag_ref) . "'");
echo ($desc) ? $desc : gTxt('smd_tag_no_desc');
} else {
send_xml_response(array('http-status' => '400 Bad Request'));
}
exit(); // Don't call page_end()
}
// ------------------------
// Remove empty URL elements (used as callback to array_filter())
function smd_tags_remove_empty($var) {
return($var != '');
}
// ------------------------
// Handle fake tag section URL
function smd_tags_url_handler($evt, $stp) {
global $smd_tag_type, $pretext, $permlink_mode;
if (!smd_tags_table_exist()) {
return;
}
$prefs = smd_tags_pref_get(array('smd_tag_u_sec', 'smd_tag_u_pnam', 'smd_tag_u_ptyp'), 1);
$urlsec = do_list($prefs['smd_tag_u_sec']['val']);
$urlnam = $prefs['smd_tag_u_pnam']['val'];
$urltyp = $prefs['smd_tag_u_ptyp']['val'];
$subpath = preg_replace("/https?:\/\/.*(\/.*)/Ui", "$1", hu);
$regsafesubpath = preg_quote($subpath, '/');
$req = preg_replace("/^$regsafesubpath/i", '/', serverSet('REQUEST_URI'));
$qs = strpos($req, '?');
$qatts = ($qs ? a.substr($req, $qs + 1) : '');
if ($qs) $req = substr($req, 0, $qs);
$parts = array_values(array_filter(explode('/', $req), 'smd_tags_remove_empty'));
$validTypes = array('article','image','file','link');
// Deal with clean URL syntax first, trying to avoid clashes with built-in permlink schemes.
if (count($parts) > 1) {
// Determine if this URL is one we care about.
// If so, find where the tag portion begins (immediately after the trigger).
$pos = false;
foreach($urlsec as $trigger) {
if (($pos = array_search($trigger, $parts)) !== false) {
$pos++; // Start of the tag info
break;
}
}
if ($pos !== false) {
// Try to detect section/id/title permlink scheme
$sit = false;
if (count($parts) == 3) {
// Note that '0' is not a valid article ID but is numeric and could be the name of a tag, so it needs special treatment
if (is_numeric($parts[1]) && $parts[1] != '0') {
$sit = true;
}
}
// As long as this is a regular permlink scheme, set the tag to be the remaining URL portion
if (!$sit) {
if (in_array($parts[$pos], $validTypes)) {
$smd_tag_type = $parts[$pos];
$pos++;
} else {
$smd_tag_type = $validTypes[0];
}
$smd_tag = join('/', array_slice($parts, $pos));
smd_tags_set($smd_tag_type, $smd_tag);
$_SERVER['QUERY_STRING'] = $qatts;
$_SERVER['REQUEST_URI'] = $subpath . $parts[0]; // Drop back to section list mode
// $_SERVER['QUERY_STRING'] = $urlnam.'='.$smd_tag .a. $urltyp.'='.$smd_tag_type . $qatts;
// $_SERVER['REQUEST_URI'] = $subpath . $parts[0] . '/?' . serverSet('QUERY_STRING');
}
}
} else if ((count($parts) == 1) && (in_array($parts[0], $urlsec) || in_array(gps('s'), $urlsec))) {
// Default or named section (or /title permlink mode) + possible messy tag syntax
$theType = gps($urltyp);
$smd_tag = gps($urlnam);
$smd_tag_type = (empty($theType) && empty($smd_tag)) ? '' : ((in_array($theType, $validTypes)) ? $theType : $validTypes[0]);
smd_tags_set($smd_tag_type, $smd_tag);
$_SERVER['QUERY_STRING'] = (($permlink_mode == 'messy') ? $urlnam.'='.$smd_tag .a. $urltyp.'='.$smd_tag_type : '') . $qatts;
$_SERVER['REQUEST_URI'] = $subpath . $parts[0] . (($permlink_mode == 'messy') ? '/?' . serverSet('QUERY_STRING') : '');
}
}
// TODO: cache?
// ------------------------
function smd_tags_set($typ, $tag='', $item='') {
global $smd_tags;
// static $smd_tagstore;
$smd_tags = array();
$prefs = smd_tags_pref_get(array('smd_tag_u_combi', 'smd_tag_u_combi_and', 'smd_tag_u_combi_or'), 1);
$combi = $prefs['smd_tag_u_combi']['val'];
$cand = $prefs['smd_tag_u_combi_and']['val'];
$cor = $prefs['smd_tag_u_combi_or']['val'];
// Multi-tag filtering
if ($tag) {
$taglist = explode($cor, $tag);
$num_or = count($taglist);
if ($num_or == 1) {
// Only one item in the exploded list could mean one of two things:
// a) only one item(!)
// b) the 'and' delimiter is in use
$andlist = explode($cand, $tag);
$num_and = count($andlist);
if ($num_and > 1) {
$smd_tags['meta']['search_mode'] = 'and';
$smd_tags['meta']['tag_head'] = $andlist[0];
$smd_tags['meta']['tag_tail'] = $andlist[count($andlist)-1];
$taglist = $andlist;
} else {
$smd_tags['meta']['search_mode'] = 'single';
$smd_tags['meta']['tag_head'] = $smd_tags['meta']['tag_tail'] = $andlist[0];
}
} else if ($num_or > 1) {
$smd_tags['meta']['search_mode'] = 'or';
$smd_tags['meta']['tag_head'] = $taglist[0];
$smd_tags['meta']['tag_tail'] = $taglist[count($taglist)-1];
}
$smd_tags['meta']['tag_list'] = $taglist;
$tag = join(',', quote_list($taglist));
}
// Override the search mode if it's switched off in prefs
if (!$combi) {
$smd_tags['meta']['search_mode'] = 'single';
}
// MONSTER!
$rs = startRows(
"SELECT id, name, txt.type, parent, title, desc_html AS description, count(item_id) AS count"
.",(SELECT CAST((rgt - lft - 1) / 2 AS UNSIGNED)) AS children"
.",(SELECT COUNT(*) FROM ".PFX.SMD_TAG." WHERE name=name" . (($typ) ? " AND type='$typ'" : "") . " AND lft < txt.lft AND rgt > txt.rgt)-1 AS level"
." FROM ".PFX.SMD_TAG." AS txt, ".PFX.SMD_TAGU." AS txu"
." WHERE tag_id = id"
.(($typ) ? " AND txt.type='$typ'" : "")
.(($tag) ? " AND name IN ($tag)" : "")
.(($item) ? " AND item_id='$item'" : "")
." GROUP BY id");
smd_tags_populate($rs, $typ);
// In case one of the URL tags isn't in use, populate $smd_tags with the tag details.
// Note this one requires type.
if (!$item) {
$rs = startRows(
"SELECT id, name, txt.type, parent, title, desc_html AS description"
.",(SELECT CAST((rgt - lft - 1) / 2 AS UNSIGNED)) AS children"
.",(SELECT COUNT(*) FROM ".PFX.SMD_TAG." WHERE type='$typ' AND name=name AND lft < txt.lft AND rgt > txt.rgt)-1 AS level"
." FROM ".PFX.SMD_TAG." AS txt"
." WHERE txt.type='$typ'"
.(($tag) ? " AND name IN ($tag)" : "")
." GROUP BY id");
smd_tags_populate($rs, $typ);
}
}
// ------------------------
function smd_tags_populate($rs, $typ) {
global $smd_tags;
while ($row = nextRow($rs)) {
$cnt = (isset($row['count'])) ? $row['count'] : '0';
$smd_tags[$typ][$row['id']] = array('tag_name' => $row['name'], 'tag_title' => $row['title'], 'tag_description' => $row['description'], 'tag_parent' => $row['parent'], 'tag_count' => $cnt, 'tag_children' => $row['children'], 'tag_level' => $row['level']);
$smd_tags[$typ]['tag_name'][$row['id']] = $row['name'];
$smd_tags[$typ]['tag_title'][$row['id']] = $row['title'];
$smd_tags[$typ]['tag_description'][$row['id']] = $row['description'];
$smd_tags[$typ]['tag_parent'][$row['id']] = $row['parent'];
$smd_tags[$typ]['tag_count'][$row['id']] = $cnt;
$smd_tags[$typ]['tag_children'][$row['id']] = $row['children'];
$smd_tags[$typ]['tag_level'][$row['id']] = $row['level'];
}
}
// ------------------------
// Add tags to the global scope, depending on context.
// Returns the current context
function smd_tags_context() {
global $thisarticle, $thisfile, $thislink, $thisimage, $smd_tag_type, $smd_tag_items, $smd_tags, $smd_thistag;
$ctxt = $smd_tag_type;
$ids = array();
$scp = isset($smd_tags[$ctxt]) ? $smd_tags[$ctxt] : array();
// Individual article or list
$id = '';
if (!empty($thisimage)) {
$id = $thisimage['id'];
$ctxt = 'image';
$scp = $thisimage;
} else if(!empty($thisfile)) {
$id = $thisfile['id'];
$ctxt = 'file';
$scp = $thisfile;
} else if(!empty($thislink)) {
$id = $thislink['id'];
$ctxt = 'link';
$scp = $thislink;
} else if(!empty($thisarticle)) {
$id = $thisarticle['thisid'];
$ctxt = 'article';
$scp = $thisarticle;
}
if ($id) {
smd_tags_set($ctxt, '', $id);
}
if(isset($smd_tags[$ctxt])) {
foreach($smd_tags[$ctxt] as $rid => $row) {
if (is_int($rid)) {
$ids[] = $rid;
}
}
}
// TODO: is this needed to overwrite if tag already set?
if (!empty($smd_thistag)) {
$ids = array($smd_thistag['tag_id']);
}
return array('context' => $ctxt, 'scope' => $scp, 'id' => $ids);
}
// ------------------------
// Load current tag into global scope
function smd_tag_populate($row) {
global $smd_thistag;
$smd_thistag['tag_id'] = $row['id'];
$smd_thistag['tag_name'] = $row['name'];
$smd_thistag['tag_lettername'] = smd_tags_utf8_substr($row['name'], 0, 1);
$smd_thistag['tag_lettertitle'] = smd_tags_utf8_substr($row['title'], 0, 1);
$smd_thistag['tag_type'] = $row['type'];
$smd_thistag['tag_parent'] = $row['parent'];
$smd_thistag['tag_title'] = $row['title'];
$smd_thistag['tag_description'] = $row['description'];
$smd_thistag['tag_children'] = $row['children'];
$smd_thistag['tag_level'] = $row['level'];
$smd_thistag['tag_count'] = $row['count'];
$smd_thistag['tag_indent'] = $row['indent'];
$smd_thistag['tag_weight'] = $row['weight'];
$smd_thistag['tag_first'] = $row['first'];
$smd_thistag['tag_last'] = $row['last'];
}
// Thanks http://us2.php.net/manual/en/function.substr.php
function smd_tags_utf8_substr($str, $start) {
preg_match_all("/./u", $str, $ar);
if(func_num_args() >= 3) {
$end = func_get_arg(2);
return join('',array_slice($ar[0],$start,$end));
} else {
return join('',array_slice($ar[0],$start));
}
}
// ------------------------
// All tags/counts
function smd_tag_list_adm($atts) {
include_once txpath.'/publish/taghandlers.php';
extract(lAtts(array(
'type' => 'article',
'indent' => '  ',
'count' => 1,
'label' => '',
'labeltag' => '',
'wraptag' => '',
'break' => '',
'class' => __FUNCTION__,
'breakclass' => '',
),$atts));
$validTypes = array('list' => 'article', 'image' => 'image', 'file' => 'file', 'link' => 'link');
$rs = smd_tag_tree('root', $type, '1=1', SMD_TAG);
$rsc = getRows("SELECT txl.tag_id, txc.name
FROM ".PFX.SMD_TAG." AS txt, ".PFX.SMD_TAGC." AS txl, ".PFX."txp_category AS txc
WHERE txt.type = '$type' AND txt.id = txl.tag_id AND txl.cat_id = txc.id");
$rscout = array();
if ($rsc) {
foreach ($rsc as $idx => $row) {
$rscout[$row['tag_id']] = $row['name'];
}
}
if ($rs) {
$out = array();
$totals = array();
$pref = smd_tags_pref_get('smd_tag_p_master', 1);
$god = $pref['smd_tag_p_master']['val'];
// Tally each tag's usage
if ($count) {
$rsu = safe_rows_start('tag_id, count(*) as num', SMD_TAGU, "type='$type' GROUP BY tag_id");
while ($row = nextRow($rsu)) {
$tagid = $row['tag_id'];
$num = $row['num'];
$ids = safe_column('item_id', SMD_TAGU, "type='$type' AND tag_id='$tagid'");
$totals[$tagid] = array($num, join(",",$ids));
}
}
foreach ($rs as $row) {
extract($row);
$parent = (isset($parent)) ? $parent : ''; // Parent might not be defined
$row['type'] = $type;
if (isset($totals[$id])) {
$row['count'] = $totals[$id][0];
$ev = array_keys($validTypes,$type);
$url = 'index.php?event='.$ev[0].a.'search_method=id'.a.'crit='.$totals[$id][1];
$cntop = sp.href('('.$totals[$id][0].')', $url);
} else {
$row['count'] = 0;
$cntop = sp.'(0)';
}
$rowdata = '<span name="smd_tagnam" class="smd_hidden">'.$name
.'</span><span name="smd_tagid" class="smd_hidden">'.$id
.'</span><span name="smd_tagttl" class="smd_hidden">'.$title
.'</span><span name="smd_tagpar" class="smd_hidden">'.$parent
.'</span><span name="smd_tagcat" class="smd_hidden">'.((isset($rscout[$id])) ? $rscout[$id] : '')
.'</span><span name="smd_tagdsc" class="smd_hidden">'.$description
.'</span>';
$out[] = array(
'data' => $rowdata.str_repeat($indent, $level * 1) . $title . (($count) ? $cntop : '').n,
'level' => $level,
'class' => ($god==$name ? array('smd_tag_master', 'smd_tag_parent') : ($god==$parent ? array('smd_tag_master','smd_tag_child') : array())),
);
// $smd_thistag = array();
}
if ($out) {
$wrapit = do_list($wraptag, ":");
if ($wrapit[0] == 'table') {
// Tables are a special case
$totalItems = $rows = count($rs);
$step = 1;
$cols = 1;
$numopts = count($wrapit);
// Each successive level overrides the previous step so by the end of the ifs,
// 3 values are set up: step, rows and cols
if ($numopts > 1) {
$cols = $wrapit[1];
$rows = ceil($totalItems/$cols);
}
if ($numopts > 2) {
if ($wrapit[2] == 'rows') {
$rows = $wrapit[1];
$cols = ceil($totalItems/$rows);
}
}
if ($numopts > 3) {
if ($wrapit[3] == 'bycol') {
$step = ($wrapit[2]=='cols') ? ceil($totalItems/$cols) : $rows;
if ($wrapit[2]=='cols') {
$rows = $step;
} else {
$cols = ceil($totalItems/$rows);
}
}
}
// Generate table based on above rules
$tblout = array();
$cellCtr = 0;
$class = (!empty($class)) ? ' class="'.$class.'"' : '';
$breakclass = (!empty($breakclass)) ? ' class="'.$breakclass.'"' : '';
for ($idx = 0; $idx < $rows; $idx++) {
$bld = array();
for ($jdx = 0; $jdx < $cols; $jdx++) {
$offset = ($wrapit[3]=='bycol') ? ($jdx * $step) + $idx : $cellCtr;
$bclass = (isset($out[$offset]['class'])) ? join(' ', $out[$offset]['class']) : '';
$bld[] = (isset($out[$offset]['data'])) ? tda($out[$offset]['data'], ($bclass ? ' class = "'.$bclass.'"': '')) : td('');
$cellCtr++;
}
$tblout[] = tr(join('', $bld), $breakclass);
}
return doLabel($label, $labeltag).tag(join(n, $tblout), $wrapit[0], $class);
} else if ($wrapit[0] == 'list') {
// Tags by list, optionally split into new row/col every N items
$totalItems = count($rs);
$lim = 0;
$style = $breakstyle = '';
$numopts = count($wrapit);
if ($numopts > 1) {
$lim = $wrapit[1];
}
if ($numopts > 3) {
if ($wrapit[3] == 'byrow') {
$style = ' style="clear:both;"';
$breakstyle = ' style="float:left;"';
} else {
$style = ' style="float:left;"';
}
}
// Generate the list
$listout = array();
$itemCtr = 0;
$class = (!empty($class)) ? ' class="'.$class.'"' : '';
foreach ($out as $item) {
$bclass = join(' ', array_merge($item['class'], array($breakclass)));
$bclass = (!empty($bclass)) ? ' class="'.$bclass.'"' : '';
if ($itemCtr == 0) {
$listout[] = "<ul$style>";
}
$listout[] = '<li'.$bclass.$breakstyle.'>'.$item['data'].'</li>';
$itemCtr++;
if (($lim > 0 && $itemCtr >= $lim) || $itemCtr >= $totalItems-1) {
$listout[] = '</ul>';
$itemCtr = 0;
}
}
return doLabel($label, $labeltag).tag(join(n, $listout), 'div', $class);
} else if ($wrapit[0] == 'listgrp') {
// Tags by list, split into new row/col every time a new level 'N' is reached
$totalItems = count($rs);
$level = 0;
$style = $breakstyle = '';
$numopts = count($wrapit);
if ($numopts > 1) {
$level = $wrapit[1];
}
if ($numopts > 3) {
if ($wrapit[3] == 'byrow') {
$style = ' class="smd_clear"';
$breakstyle = ' class="smd_freeform_byrow"';
} else {
$style = ' class="smd_freeform_bycol"';
}
}
// Generate the list
$listout = array();
$itemCtr = 0;
$class = (!empty($class)) ? ' class="'.$class.'"' : '';
$prevel = 0;
foreach ($out as $item) {
$bclass = join(' ', array_merge($item['class'], array($breakclass)));
$bclass = (!empty($bclass)) ? ' class="'.$bclass.'"' : '';
if ($prevel >= $item['level'] && $item['level'] <= $level) {
if ($itemCtr > 0) {
$listout[] = '</ul>';
}
$listout[] = "<ul$style>";
}
$listout[] = '<li'.$bclass.$breakstyle.'>'.$item['data'].'</li>';
$prevel = $item['level'];
if ($itemCtr >= $totalItems-1) {
$listout[] = '</ul>';
}
$itemCtr++;
}
return doLabel($label, $labeltag).tag(join(n, $listout), 'div', $class);
} else {
//TODO: only use the data column from $out
return doLabel($label, $labeltag).doWrap($out, $wrapit[0], $break, $class, $breakclass);
}
}
return '';
}
}
// -------------------------------------------------------------
// Cloned getTree() from txplib_db.php because that function doesn't extract description
function smd_tag_tree($root, $type, $where='1=1', $tbl='txp_category') {
$root = doSlash($root);
$type = doSlash($type);
$rs = safe_row(
"lft as l, rgt as r",
$tbl,
"name='$root' and type = '$type'"
);
if (!$rs) return array();
extract($rs);
$out = array();
$right = array();
$rs = safe_rows_start(
"id, name, lft, rgt, parent, title, description, desc_html",
$tbl,
"lft between $l and $r and type = '$type' and name != 'root' and $where order by lft asc"
);
while ($rs and $row = nextRow($rs)) {
extract($row);
while (count($right) > 0 && $right[count($right)-1] < $rgt) {
array_pop($right);
}
$out[] =
array(
'id' => $id,
'name' => $name,
'title' => $title,
'level' => count($right),
'children' => ($rgt - $lft - 1) / 2,
'description' => $description,
'desc_html' => $desc_html,
'parent' => $parent,
);
$right[] = $rgt;
}
return($out);
}
// ------------------------
// Find some value in a tree and return the matching entries
function smd_tag_tree_search($val, $tree, $limit=0, $field='name') {
$ctr = 0;
$out = array();
foreach ($tree as $idx => $entry) {
if ($entry[$field] == $val) {
$out[] = $tree[$idx];
$ctr++;
}
if ($limit > 0 && $ctr == $limit) {
break;
}
}
return $out;
}
// ------------------------
// PHP4 equivalent of array_combine (from http://www.php.net/manual/en/function.array-combine.php)
if (!function_exists('array_combine')) {
function array_combine($arr1, $arr2) {
$out = array();
$arr1 = array_values($arr1);
$arr2 = array_values($arr2);
foreach($arr1 as $key1 => $value1) {
$out[(string)$value1] = $arr2[$key1];
}
return $out;
}
}
// ------------------------
// PUBLIC TAGS
// ------------------------
// Check tags of a particular type, or those that have a specific property in context.
//TODO: Think about checking logic, eg, name="business|pleasure" (OR) name="business+pleasure" (AND)
function smd_if_tag ($atts, $thing) {
global $smd_tags, $smd_tag_type, $smd_thistag;
if (!smd_tags_table_exist()) {
trigger_error(gTxt('smd_tag_not_available'));
return;
}
extract(lAtts(array(
'type' => NULL,
'id' => NULL,
'name' => NULL,
'title' => NULL,
'description' => NULL,
'parent' => NULL,
'count' => NULL,
'children' => NULL,
'level' => NULL,
'is' => NULL,
),$atts));
// Validate atts
$validTypes = array('article','image','file','link');
$ctxt = smd_tags_context();
$ctype = $ctxt['context'];
$type = (in_array($type, $validTypes)) ? $type : ( ($ctxt['context']) ? $ctxt['context'] : ( ($smd_tag_type) ? $smd_tag_type : $validTypes[0] ) );
$eqtest = array();
$nutest = array();
$opRE = '/(\>|\>\=|\<|\<\=|\!)([0-9a-zA-Z- ]+)/';
// Master tag?
$pref = smd_tags_pref_get('smd_tag_p_master', 1);
$god = $pref['smd_tag_p_master']['val'];
$eqtest['master'] = (($is == 'master') && ($god != '')) ? $god : NULL;
// Start / end of list?
$eqtest['first'] = ($is == 'first') ? 1 : NULL;
$eqtest['last'] = ($is == 'last') ? 1 : NULL;
// TODO: Consolidate this into a loop for each 'id', 'name', type', 'title', ...
$num = preg_match_all($opRE, $id, $parts);
$eqtest['id'] = ($num<=0) ? $id : NULL;
if ($num>0) $nutest['id'] = array($parts[1][0] => $parts[2][0]);
$num = preg_match_all($opRE, $name, $parts);
$eqtest['name'] = ($num<=0) ? $name : NULL;
if ($num>0) $nutest['name'] = array($parts[1][0] => $parts[2][0]);
$num = preg_match_all($opRE, $type, $parts);
$eqtest['type'] = ($num<=0) ? $type : NULL;
if ($num>0) $nutest['type'] = array($parts[1][0] => $parts[2][0]);
$num = preg_match_all($opRE, $title, $parts);
$eqtest['title'] = ($num<=0) ? $title : NULL;
if ($num>0) $nutest['title'] = array($parts[1][0] => $parts[2][0]);
$num = preg_match_all($opRE, $description, $parts);
$eqtest['description'] = ($num<=0) ? $description : NULL;
if ($num>0) $nutest['description'] = array($parts[1][0] => $parts[2][0]);
$num = preg_match_all($opRE, $parent, $parts);
$eqtest['parent'] = ($num<=0) ? $parent : NULL;
if ($num>0) $nutest['parent'] = array($parts[1][0] => $parts[2][0]);
$num = preg_match_all($opRE, $count, $parts);
$eqtest['count'] = ($num<=0) ? $count : NULL;
if ($num>0) $nutest['count'] = array($parts[1][0] => $parts[2][0]);
$num = preg_match_all($opRE, $children, $parts);
$eqtest['children'] = ($num<=0) ? $children : NULL;
if ($num>0) $nutest['children'] = array($parts[1][0] => $parts[2][0]);
$num = preg_match_all($opRE, $level, $parts);
$eqtest['level'] = ($num<=0) ? $level : NULL;
if ($num>0) $nutest['level'] = array($parts[1][0] => $parts[2][0]);
trace_add('[smd_if_tag equality tests: '. join (', ', $eqtest) . ']');
trace_add('[smd_if_tag numeric tests: '. print_r($nutest, true) . ']');
// Init
$out = $result = $numTests = 0;
if (!isset($smd_tags[$type]) && empty($smd_thistag)) {
// not in scope
} else {
// Equality comparisons
foreach ($eqtest as $tname => $tval) {
if (!is_null($tval)) {
$numTests++;
// Master is a special case which checks if the parent is the master tag
$tname = ($tname == 'master') ? 'parent' : $tname;
if ($smd_thistag) {
// Local scope
if ($smd_thistag['tag_'.$tname] == $tval) {
$out++;
}
} else {
// Global scope
if ($tname == "type") {
if ($smd_tag_type == $type || $ctype == $type) {
$out++;
}
}
if (isset($smd_tags[$type]['tag_'.$tname]) && in_array($tval, $smd_tags[$type]['tag_'.$tname], empty($tval) && $tval !== '0')) {
$out++;
}
}
}
}
// Numeric comparisons
foreach ($nutest as $tname => $tval) {
$numTests++;
$op = current(array_keys($tval));
$val = current($tval);
if ($smd_thistag) {
$comparison = $smd_thistag['tag_'.$tname];
} else {
$comparison = $smd_tags[$type]['tag_'.$tname];
}
switch ($op) {
case '>':
if (isset($comparison) && $comparison > $val) {
$out++;
}
break;
case '>=':
if (isset($comparison) && $comparison >= $val) {
$out++;
}
break;
case '<':
if (isset($comparison) && $comparison < $val) {
$out++;
}
break;
case '<=':
if (isset($comparison) && $comparison <= $val) {
$out++;
}
break;
case '!':
if (isset($comparison) && $comparison != $val) {
$out++;
}
break;
}
}
trace_add('[smd_if_tag num tests | results: ' . $numTests . '|' . $out . ']');
// Count how many successes there were
if ($numTests == $out) {
$result = 1;
}
}
return parse(EvalElse($thing, $result));
}
// ------------------------
function smd_if_tag_list ($atts, $thing) {
global $smd_tag_type;
return parse(EvalElse($thing, (!empty($smd_tag_type))));
}
// ------------------------
// Return name/title of current tag
function smd_tag_name($atts, $thing='') {
global $smd_thistag, $permlink_mode, $plugins;
if (!smd_tags_table_exist()) {
trigger_error(gTxt('smd_tag_not_available'));
return;
}
extract(lAtts(array(
'title' => 1,
'link' => '',
'section' => '',
'cleanurls' => 1,
'parent' => 0, // not useful to print but good for URLs to nav back up the tree
'parentlabel' => 'Up a level',
'wraptag' => '',
'class' => __FUNCTION__,
'style' => '',
'pad_str' => '',
'pad_pos' => 'left', // left/right/both with optional :in suffix to indicate if it's to go inside the link
),$atts));
$smdpref = smd_tags_pref_get(array('smd_tag_u_sec', 'smd_tag_u_pnam', 'smd_tag_u_ptyp'), 1);
$urlsec = do_list($smdpref['smd_tag_u_sec']['val']);
$section = ($section) ? $section : $urlsec[0];
$in_default = (in_array($section, $urlsec));
$urlnam = $smdpref['smd_tag_u_pnam']['val'];
$urltyp = $smdpref['smd_tag_u_ptyp']['val'];
// gbp_permanent_links sets messy mode behind the scenes but still uses non-messy URLs
// so it requires an exception
$gbp_pl = (is_array($plugins) && in_array('gbp_permanent_links', $plugins));
$messy = ($permlink_mode == 'messy') && (!$gbp_pl);
$parentlabel = ($parentlabel) ? $parentlabel : (($parent) ? $smd_thistag['tag_parent'] : $smd_thistag['tag_name']);
$label = ($parent) ? $parentlabel : (($title) ? $smd_thistag['tag_title'] : $smd_thistag['tag_name']);
$tname = ($parent) ? $smd_thistag['tag_parent'] : $smd_thistag['tag_name'];
$level = $smd_thistag['tag_level'];
$style = ($style) ? ' style="'.$style.'"' : '';
$padding = ($pad_str) ? str_repeat($pad_str, $level) : '';
$pad_pos = do_list($pad_pos, ':');
$pad['lin'] = ($padding && (in_array('left', $pad_pos) !== false || in_array('both', $pad_pos) !== false) && in_array('in', $pad_pos) !== false) ? $padding : '';
$pad['rin'] = ($padding && (in_array('right', $pad_pos) !== false || in_array('both', $pad_pos) !== false) && in_array('in', $pad_pos) !== false) ? $padding : '';
$pad['l'] = ($padding && (in_array('left', $pad_pos) !== false || in_array('both', $pad_pos) !== false) && (empty($pad['lin']) || !$link)) ? $padding : '';
$pad['r'] = ($padding && (in_array('right', $pad_pos) !== false || in_array('both', $pad_pos) !== false) && (empty($pad['rin']) || !$link)) ? $padding : '';
$dest = ($messy || !$cleanurls)
? pagelinkurl(array('s' => $section, $urlnam => $tname, $urltyp => $smd_thistag['tag_type']))
: hu.$section.'/'.(($in_default) ? '' : $urlsec[0].'/').$smd_thistag['tag_type'].'/'.$tname;
if ($thing) {
$out = '<a'.
($messy ? '' : ' rel="tag"').
( ($class and !$wraptag) ? ' class="'.$class.'"' : '' ).
' href="'.$dest.'"'.
($title ? ' title="'.$label.'"' : '').
'>'.parse($thing).'</a>';
} elseif ($link) {
$out = $pad['l'].'<a'.
($messy ? '' : ' rel="tag"').
' href="'.$dest.'">'.$pad['lin'].$label.$pad['rin'].'</a>'.$pad['r'];
} else {
$out = $pad['l'].$label.$pad['r'];
}
return doTag($out, $wraptag, $class, $style);
}
// ------------------------
// Return # of items associated with this tag
//TODO: think about per-section / per-category counts
function smd_tag_count($atts, $thing='') {
global $smd_thistag, $smd_tags;
if (!smd_tags_table_exist()) {
trigger_error(gTxt('smd_tag_not_available'));
return;
}
extract(lAtts(array(
'class' => __FUNCTION__,
'style' => '',
'wraptag' => '',
'wrapcount' => ' (:)',
'showempty' => '1',
'paramdelim' => ':',
),$atts));
$wrapcount = explode($paramdelim, $wrapcount); // do_list does a trim: don't want that
$style = ($style) ? ' style="'.$style.'"' : '';
if (count($wrapcount) == 1) {
$wrapcount[1] = $wrapcount[0];
}
if ($smd_thistag) {
$out = $smd_thistag['tag_count'];
} else {
$out = isset($smd_tags['meta']['content_count']) ? $smd_tags['meta']['content_count'] : '';
}
$out = ($out == 0 && !$showempty) ? '' : $wrapcount[0].$out.$wrapcount[1];
return doTag($out, $wraptag, $class, $style);
}
// ------------------------
// Return other info about the current tag
function smd_tag_info($atts, $thing='') {
global $smd_thistag;
if (!smd_tags_table_exist()) {
trigger_error(gTxt('smd_tag_not_available'));
return;
}
extract(lAtts(array(
'item' => 'name',
'wraptag' => '',
'break' => 'br',
'class' => __FUNCTION__,
'breakclass' => '',
),$atts));
$out = array();
$availableItems = array('id','name','title','description','lettername','lettertitle','type','parent','children','level','count','indent', 'weight');
$items = do_list($item);
foreach ($items as $whatnot) {
if (in_array($whatnot, $availableItems)) {
$out[] = $smd_thistag['tag_'.$whatnot];
}
}
return doWrap($out, $wraptag, $break, $class, $breakclass);
}
// ------------------------
// Related articles/images/files/links by tag
function smd_related_tags($atts, $thing='') {
global $thisarticle, $thisfile, $thislink, $thisimage, $pretext, $prefs, $smd_tags, $smd_thistag;
static $smd_tags_pc;
if (!smd_tags_table_exist()) {
trigger_error(gTxt('smd_tag_not_available'));
return;
}
extract(lAtts(array(
'type' => '',
'section' => $pretext['s'],
'status' => '4',
'limit' => 99999,
'offset' => 0,
'form' => '',
'match' => 'tag_name',
'match_self' => 0,
'no_widow' => @$prefs['title_no_widow'],
'sort' => '',
'label' => '',
'labeltag' => '',
'wraptag' => '',
'break' => 'br',
'class' => __FUNCTION__,
'delim' => ',',
'paramdelim' => ':',
'debug' => '0',
),$atts));
// Validate atts
$validTypes = array('article','image','file','link');
$ctxt = smd_tags_context();
$scope = $ctxt['scope'];
$idlist = $ctxt['id'];
$ctype = $ctxt['context'];
$type = (in_array($type, $validTypes)) ? $type : (($ctxt['context']) ? $ctxt['context'] : $validTypes[0]);
if (!isset($smd_tags_pc[$type])) {
$smd_tags_pc[$type] = array(
'all' => array(),
'parlist' => array(),
'chilist' => array(),
);
}
$sectionClause = ($section) ? " AND txp.Section IN ('".join("','", doSlash(do_list($section)))."')" : '';
$status = do_list($status);
$stati = array();
foreach ($status as $stat) {
if (empty($stat)) {
continue;
} else if (is_numeric($stat)) {
$stati[] = $stat;
} else {
$stati[] = getStatusNum($stat);
}
}
$statSQL = ' AND txp.Status IN ('.join(',', $stati).')';
$out = array();
if (!$sort) {
switch ($type) {
case "article":
$sort = "Posted desc";
break;
case "image":
case "link":
$sort = "date desc";
break;
case "file":
$sort = "created desc";
break;
}
}
$cfs = getCustomfields();
$cfKeys = implode(',', array_map(
function($k) { return 'custom_' . $k; }, array_keys($cfs)
));
// Lookup table for making SQL queries
$sqlStubs = array(
"article" => array(
"select" => "txp.ID, Posted, Expires, AuthorID, LastMod, LastModID, txp.Title, Title_html, Body, Body_html, Excerpt, Excerpt_html, Image, Category1, Category2, Annotate, AnnotateInvite, comments_count, Status, textile_body, textile_excerpt, Section, override_form, Keywords, txp.description, url_title" . ($cfKeys ? ','. $cfKeys : '') . ", uid, feed_time, position, unix_timestamp(Posted) as uPosted, unix_timestamp(LastMod) as uLastMod, unix_timestamp(Expires) as uExpires, COUNT(smt.name) as tag_sum",
"table" => "textpattern",
"gtags" => $thisarticle,
"gid" => "thisid",
),
"image" => array(
"select" => "txp.id, txp.name, txp.category, txp.ext, txp.w, txp.h, txp.alt, txp.caption, txp.date, txp.author, txp.thumb_w, txp.thumb_h, txp.thumbnail, tu.item_id, tu.tag_id, smt.id AS smtid, smt.name AS smtname, smt.desc_html AS smtdescription, smt.type, smt.parent, smt.lft, smt.rgt, smt.title, COUNT(smt.name) as tag_sum",
"table" => "txp_image",
"gtags" => $thisimage,
"gid" => "id",
),
"file" => array(
"select" => "txp.id, txp.filename, txp.title, txp.category, txp.permissions, txp.description, txp.downloads, txp.status, txp.modified, txp.created, txp.size, tu.item_id, tu.tag_id, smt.id AS smtid, smt.name AS smtname, smt.desc_html AS smtdescription, smt.type, smt.parent, smt.lft, smt.rgt, smt.title, COUNT(smt.name) as tag_sum",
"table" => "txp_file",
"gtags" => $thisfile,
"gid" => "id",
),
"link" => array(
"select" => "txp.id, txp.date, txp.category, txp.url, txp.linkname, txp.linksort, txp.description, tu.item_id, tu.tag_id, smt.id AS smtid, smt.name AS smtname, smt.desc_html AS smtdescription, smt.type, smt.parent, smt.lft, smt.rgt, smt.title, COUNT(smt.name) as tag_sum",
"table" => "txp_link",
"gtags" => $thislink,
"gid" => "id",
),
);
// Extract stuff to match & make up query replacement variables
$match = do_list($match, $paramdelim);
$matchType = array_shift($match);
if (count($match) == 0) {
// Assume current type unless a tag is being matched (in which case, use $ctype)
$matchWith = array($matchType);
$matchType = (strpos($matchWith[0], 'tag_') !== false) ? $ctype : $type;
} else {
$matchWith = $match;
}
$matches = array();
foreach ($matchWith as $matchItem) {
if (strpos($matchItem, 'tag_') !== false) {
$lookin = (isset($smd_tags[$matchType])) ? $smd_tags[$matchType] : ( (isset($smd_thistag)) ? $smd_thistag : array() );
} else {
$lookin = $sqlStubs[$matchType]["gtags"];
}
if (isset($lookin[$matchItem])) {
$thismatch = $lookin[$matchItem];
if (is_array($thismatch)) {
foreach ($thismatch as $subID => $subItem) {
if (in_array($subID, $idlist)) {
$matches[] = $subItem;
}
}
} else {
$matches[] = $thismatch;
}
}
}
$matches = array_unique($matches);
// Should the tag hierarchy be taken into account?
$opts = smd_tags_pref_get(array('smd_tag_p_lbi', 'smd_tag_u_combi'));
$bidir = $opts['smd_tag_p_lbi']['val'];
$combi = $opts['smd_tag_u_combi']['val'];
if ($matches) {
$parents = $children = array();
if ($smd_tags_pc[$type]['all']) {
$full_tag_tree = $smd_tags_pc[$type]['all'];
} else {
$full_tag_tree = $smd_tags_pc[$type]['all'] = smd_tag_tree('root', $type, '1=1', SMD_TAG);
}
// Build two lists from the full tag tree:
// 1) parlist: each node's parent
// 2) chilist: list of nodes below the current
if (!$smd_tags_pc[$type]['parlist'] || !$smd_tags_pc[$type]['chilist']) {
foreach ($full_tag_tree as $node) {
$smd_tags_pc[$type]['parlist'][$node['name']] = $node['parent'];
if ($node['parent'] == 'root') {
$smd_tags_pc[$type]['chilist'][$node['name']] = array();
} else {
$smd_tags_pc[$type]['chilist'][$node['parent']][] = $node['name'];
foreach ($smd_tags_pc[$type]['chilist'] as $key => $nodes) {
if (in_array($node['parent'], $nodes)) {
$smd_tags_pc[$type]['chilist'][$key][] = $node['name'];
}
}
}
}
}
// Reverse lookup to find parents of child
if ($bidir==1 || $bidir==3) {
foreach ($matches as $matchit) {
$theparent = $matchit;
while (array_key_exists($theparent, $smd_tags_pc[$type]['parlist'])) {
if ($smd_tags_pc[$type]['parlist'][$theparent] == 'root') {
break;
}
$parents[] = $theparent = $smd_tags_pc[$type]['parlist'][$theparent];
}
}
}
// Forward lookup to find children of parent
if ($bidir==2 || $bidir==3) {
foreach ($matches as $matchit) {
if (array_key_exists($matchit, $smd_tags_pc[$type]['chilist'])) {
$children = array_merge($children, $smd_tags_pc[$type]['chilist'][$matchit]);
}
}
}
$matches = array_unique(array_merge( $matches, $parents, $children ));
}
trace_add('[smd_related_tags matches: ' . join(', ', $matches) . ']');
// Convert above opts to SQL query clauses
$excludeClause = ($match_self) ? '' : ' AND txp.id != "'.$sqlStubs[$type]["gtags"][$sqlStubs[$type]["gid"]].'"';
$and_mode = (isset($smd_tags['meta']['search_mode']) && ($smd_tags['meta']['search_mode'] == 'and')) ? true : false;
$and_modeClause = ($and_mode && $matches) ? ' HAVING COUNT(*) = ' . count($matches) : '';
$last_tag = (isset($smd_tags['meta']['tag_tail'])) ? $smd_tags['meta']['tag_tail'] : '';
$matchClause = " AND " .(($matches) ? ($combi ? "smt.name IN (" .join(",", quote_list($matches)). ")" : "smt.name = '".$last_tag."'") : "smt.name = ''");
$orderBy = " ORDER BY " .$sort. " LIMIT " .$offset. "," .$limit;
$contentCount = 0;
switch ($type) {
case "article":
$rs = getRows("SELECT SQL_CALC_FOUND_ROWS " . $sqlStubs[$type]["select"] . " FROM " . safe_pfx($sqlStubs[$type]["table"]) . " AS txp
LEFT JOIN ".PFX.SMD_TAGU. " AS tu ON txp.id = tu.item_id
LEFT JOIN ".PFX.SMD_TAG. " AS smt ON tu.tag_id = smt.id
WHERE tu.type = 'article'" . $statSQL . $excludeClause . $sectionClause . $matchClause . ' GROUP BY txp.id' . $and_modeClause . $orderBy, $debug);
$contentResult = mysqli_fetch_assoc(safe_query('SELECT FOUND_ROWS() AS found', $debug));
$contentCount = $contentResult['found'];
if ($rs) {
trace_add('[smd_related_tags article records: ' . print_r($rs, true) . ']');
$uniqrs = array();
foreach ($rs as $row) {
if (!in_array($row['ID'], $uniqrs)) {
$safe = ($thisarticle) ? $thisarticle : NULL;
populateArticleData($row);
$row['Title'] = ($no_widow) ? noWidow(escape_title($row['Title'])) : escape_title($row['Title']);
$out[] = ($form) ? parse_form($form) : (($thing) ? parse($thing) : href($row['Title'], permlinkurl($row)));
$uniqrs[] = $row['ID'];
$thisarticle = $safe;
}
}
}
break;
case "image":
$rs = getRows("SELECT SQL_CALC_FOUND_ROWS " . $sqlStubs[$type]["select"] . " FROM " . safe_pfx($sqlStubs[$type]["table"]) . " AS txp
LEFT JOIN ".PFX.SMD_TAGU. " AS tu ON txp.id = tu.item_id
LEFT JOIN ".PFX.SMD_TAG. " AS smt ON tu.tag_id = smt.id
WHERE tu.type = 'image'" . $excludeClause . $matchClause . ' GROUP BY txp.id' . $and_modeClause . $orderBy, $debug);
$contentResult = mysqli_fetch_assoc(safe_query('SELECT FOUND_ROWS() AS found', $debug));
$contentCount = $contentResult['found'];
if ($rs) {
trace_add('[smd_related_tags image records: ' . print_r($rs, true) . ']');
$uniqrs = array();
foreach ($rs as $row) {
if (!in_array($row['id'], $uniqrs)) {
$safe = ($thisimage) ? $thisimage : NULL;
$thisimage = image_format_info($row);
$out[] = ($form) ? parse_form($form) : (($thing) ? parse($thing) : image(array('id' => $row['id'])));
$uniqrs[] = $row['id'];
$thisimage = $safe;
}
}
}
break;
case "file":
$rs = getRows("SELECT SQL_CALC_FOUND_ROWS " . $sqlStubs[$type]["select"] . " FROM " . safe_pfx($sqlStubs[$type]["table"]) . " AS txp
LEFT JOIN ".PFX.SMD_TAGU. " AS tu ON txp.id = tu.item_id
LEFT JOIN ".PFX.SMD_TAG. " AS smt ON tu.tag_id = smt.id
WHERE tu.type = 'file'" . $statSQL . $excludeClause . $matchClause . ' GROUP BY txp.id' . $and_modeClause . $orderBy, $debug);
$contentResult = mysqli_fetch_assoc(safe_query('SELECT FOUND_ROWS() AS found', $debug));
$contentCount = $contentResult['found'];
if ($rs) {
trace_add('[smd_related_tags file records: ' . print_r($rs, true) . ']');
$uniqrs = array();
foreach ($rs as $row) {
if (!in_array($row['id'], $uniqrs)) {
$safe = ($thisfile) ? $thisfile : NULL;
$thisfile = file_download_format_info($row);
$out[] = ($form) ? parse_form($form) : (($thing) ? parse($thing) : file_download_link(array('filename' => $row['filename']), $row['filename']));
$uniqrs[] = $row['id'];
$thisfile = $safe;
}
}
}
break;
case "link":
$rs = getRows("SELECT SQL_CALC_FOUND_ROWS " . $sqlStubs[$type]["select"] . " FROM " . safe_pfx($sqlStubs[$type]["table"]) . " AS txp
LEFT JOIN ".PFX.SMD_TAGU. " AS tu ON txp.id = tu.item_id
LEFT JOIN ".PFX.SMD_TAG. " AS smt ON tu.tag_id = smt.id
WHERE tu.type = 'link'" . $excludeClause . $matchClause . ' GROUP BY txp.id' . $and_modeClause . $orderBy, $debug);
$contentResult = mysqli_fetch_assoc(safe_query('SELECT FOUND_ROWS() AS found', $debug));
$contentCount = $contentResult['found'];
if ($rs) {
trace_add('[smd_related_tags link records: ' . print_r($rs, true) . ']');
$uniqrs = array();
foreach ($rs as $row) {
if (!in_array($row['id'], $uniqrs)) {
$safe = ($thislink) ? $thislink : NULL;
$thislink = array(
'id' => $row['id'],
'linkname' => $row['linkname'],
'url' => $row['url'],
'description' => $row['description'],
'date' => $row['date'],
'category' => $row['category'],
);
$out[] = ($form) ? parse_form($form) : (($thing) ? parse($thing) : href($row['linkname'], $row['url']));
$uniqrs[] = $row['id'];
$thislink = $safe;
}
}
}
break;
}
$smd_tags['meta']['content_count'] = $contentCount;
if ($out) {
return doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class);
}
return '';
}
// ------------------------
// List tags from current context, or given type
function smd_tag_list($atts, $thing='') {
global $smd_tags, $smd_thistag;
static $smd_tags_tree = array();
static $smd_tags_count = array();
if (!smd_tags_table_exist()) {
trigger_error(gTxt('smd_tag_not_available'));
return;
}
extract(lAtts(array(
'flavour' => 'list', // 1) list: only tags with assignments, 2) cloud (adds weighting to smd_thistag), 3) head (first tag in list), 4) tail (last tag in list)
'showall' => '0', // 0: just used, 1: include empties
'type' => '',
'id' => '',
'name' => '',
'exclude' => '',
'parent' => '', // Start list from here
'sublevel' => '', // Get tags from this sub-level only (0=top level, 1-level 1, ..., all=everything)
'auto_detect' => '1',
'offset' => 0,
'limit' => 999999,
'form' => '',
'indent' => '  ',
'section_link' => '',
'sort' => '',
'shuffle' => 0,
'label' => '',
'labeltag' => '',
'wraptag' => 'ul',
'break' => 'li',
'class' => __FUNCTION__,
'active_class' => '', // TODO
'breakclass' => '',
),$atts));
// Validate client side atts
$validTypes = array('article','image','file','link');
$ids = '';
$where = array();
$ctxt = smd_tags_context();
$attemptMatch = (empty($name) && empty($id) && empty($exclude) && !empty($ctxt['id'])) ? false : true;
$type = (in_array($type, $validTypes)) ? $type : (($ctxt['context']) ? $ctxt['context'] : $validTypes[0]);
$ids = ($id) ? do_list($id) : (($auto_detect && $ctxt['id']) ? $ctxt['id'] : '');
// Unique identifiers for cacheing the lists
$gopts = $name.$id.$exclude.$parent.$sublevel;
$guid = md5($gopts);
if ($name) {
$name = "name IN (" .join(',', quote_list(do_list($name))). ")";
$where[] = $name;
$ids = ''; // name trumps id; TODO: maybe offer a way of combining them?
}
if ($ids) {
$ids = "id IN (" .join(',', quote_list($ids)). ")";
$where[] = $ids;
}
if ($exclude) {
$exclude = "name NOT IN (" .join(',', quote_list(do_list($exclude))). ")";
$where[] = $exclude;
}
if (strtolower($parent) == 'smd_auto') {
$parent = '';
$plist = (isset($smd_tags[$type])) ? $smd_tags[$type]['tag_name'] : '';
if (is_array($plist)) {
foreach ($plist as $idx => $pname) {
$parent = $pname;
break;
}
}
}
$where = join(' AND ', $where);
$oper = '';
if (!$where && isset($smd_tags[$type])) {
// Use URL tags
if ($showall) {
if (isset($smd_tags_tree[$type][$guid]['all'])) {
$rs = $smd_tags_tree[$type][$guid]['all'];
} else {
$rs = $smd_tags_tree[$type][$guid]['all'] = smd_tag_tree((($parent) ? $parent : 'root'), $type, '1=1', SMD_TAG);
}
} else {
// Was just the parent attribute supplied?
if ($parent) {
$rs = $smd_tags_tree[$type][$guid]['all'] = smd_tag_tree($parent, $type, '1=1', SMD_TAG);
} else {
$rs = $smd_tags[$type];
}
}
} else {
if (!$where && $attemptMatch && !$showall) {
$where = '';
$rs = array();
} else {
$where = ($where) ? $where : '1=1';
}
if ($where) {
trace_add('[smd_tag_list query: ' . $where . ']');
}
if ($where) {
if ( ($showall || $where == '1=1') && isset($smd_tags_tree[$type][$guid]['all']) ) {
$rs = $smd_tags_tree[$type][$guid]['all'];
} else {
$windex = md5($gopts.$where);
if (isset($smd_tags_tree[$type][$windex])) {
$rs = $smd_tags_tree[$type][$windex];
} else {
$rs = $smd_tags_tree[$type][$windex] = smd_tag_tree((($parent) ? $parent : 'root'), $type, (($showall) ? '1=1' : $where), SMD_TAG);
}
}
}
$sublevel = ($parent && $sublevel=='') ? ((smd_tag_tree_search($parent, $rs, 1)) ? '>=1' : '>=0') : $sublevel;
preg_match('/([=<>]+)?([]+|all)/', $sublevel, $matches);
if($matches) {
$oper = ($matches[1] && in_list($matches[1], '>, <, >=, <=')) ? $matches[1] : '==';
$sublevel=$matches[2];
} else {
$oper = '==';
}
trace_add('[smd_tag_list sublevel | operator | matches: ' . $sublevel . '|' . $oper . '|' . join(', ', $matches) . ']');
}
trace_add('[smd_tag_list tree: ' . print_r($rs, true) . ']');
if ($rs) {
$out = array();
$totals = array();
$outsub = array();
while (list($key, $row) = each($rs)) {
// Only add the row if one of:
// 1) showall is on
// 2) sublevel = 'all'
// 3) no parent specified
// 4) sublevel is equal/greater/less than the row's level
if ($showall == 1 || strtolower($sublevel) == "all" || !$parent || ($oper=='=='? $row['level'] == $sublevel : (($oper=='>=') ? $row['level'] >= $sublevel : (($oper=='<=') ? $row['level'] <= $sublevel : (($oper=='>') ? $row['level'] > $sublevel : $row['level'] < $sublevel) ) ) ) ) {
$outsub[] = $row;
}
}
$rs = $outsub;
trace_add('[smd_tag_list sublevel records: ' . print_r($rs, true) . ']');
if ($shuffle) {
shuffle($rs);
} else if ($sort) {
$sortPrefix = "SORT_";
$sortOrder = array();
$sort = do_list($sort);
$nsrt = count($sort);
for ($idx = 0; $idx < $nsrt; $idx++) {
$sort_dir = explode(' ', $sort[$idx]);
if (count($sort_dir) <= 1) {
$sort_dir[1] = "asc";
}
$direction = ($sort_dir[1] == "desc") ? $sortPrefix.'DESC' : $sortPrefix.'ASC';
$sortOrder[] = array("col" => $sort_dir[0], "sort" => $direction);
}
// Translate the rows into columns that can be sorted
foreach($rs as $key => $row) {
foreach ($row as $identifier => $item) {
$varname = "col_".$identifier;
${$varname}[$key] = $item;
}
}
// Make up an array_multisort arg list and execute it.
// The necessary evil() is because we don't know how many cols there will be in the array
for ($idx = 0; $idx < count($sortOrder); $idx++) {
$sortargs[] = '$col_'.$sortOrder[$idx]['col'];
$sortargs[] = $sortOrder[$idx]['sort'];
}
$sortit = 'array_multisort('.join(", ",$sortargs).', $rs);';
eval($sortit);
}
$rs = array_slice($rs, $offset, $limit);
trace_add('[smd_tag_list post-filter records: ' . print_r($rs, true) . ']');
$numrecs = count($rs);
// Cache the relevant info from the tag usage table
if (!isset($smd_tags_count[$type])) {
$taguse = safe_rows('tag_id, count(*) AS tally', SMD_TAGU, "type='" . doSlash($type) . "' GROUP BY tag_id");
foreach ($taguse as $tagblob) {
$smd_tags_count[$type][$tagblob['tag_id']] = $tagblob['tally'];
}
}
// Set up the cloud weighting if desired
$flavour = do_list($flavour, ':');
if ($flavour[0] == 'cloud') {
$minPercent = (isset($flavour[1]) && !empty($flavour[1])) ? $flavour[1] : 100;
$maxPercent = (isset($flavour[2]) && !empty($flavour[2])) ? $flavour[2] : 200;
$max = 1; $min = 99999; $count = 0;
foreach ($rs as $row) {
$count = (isset($smd_tags_count[$type][$row['id']])) ? $smd_tags_count[$type][$row['id']] : 0;
$max = ($count > $max ? $count : $max);
$min = ($min > $count ? $count : $min);
}
$multiplier = ($max > $min) ? ($maxPercent - $minPercent) / ($max - $min) : 1;
}
$tag_head = (isset($smd_tags['meta']['tag_head'])) ? $smd_tags['meta']['tag_head'] : '';
$tag_tail = (isset($smd_tags['meta']['tag_tail'])) ? $smd_tags['meta']['tag_tail'] : '';
$tag_list = (isset($smd_tags['meta']['tag_list'])) ? $smd_tags['meta']['tag_list'] : array();
// Preserve the current tag so lists in lists can be created
$old_tag = $smd_thistag;
// Iterate over the tags, populate the local context (global!) variable and parse through the form/container
foreach ($rs as $idx => $row) {
$index = $idx; // Because we can muck about with this value without affecting the loop counter
if (($flavour[0] == 'head') && ($row['name'] != $tag_head)) continue;
if (($flavour[0] == 'tail') && ($row['name'] != $tag_tail)) continue;
$row['type'] = $type;
$row['count'] = (isset($smd_tags_count[$type][$row['id']])) ? $smd_tags_count[$type][$row['id']] : 0;
$row['indent'] = str_repeat($indent, $row['level'] * 1);
$row['weight'] = ($flavour[0] == 'cloud') ? floor($minPercent + (($max - ( $max - ($row['count'] - $min))) * $multiplier)) : 1;
if ( ($flavour[0] == 'crumb') && (($pos = array_search($row['name'], $tag_list)) !== false) ) {
// Override the position of this entry in the list to fake a crumbtrail
$index = $pos;
}
$row['first'] = ($index == 0) ? 1 : 0;
$row['last'] = ($index == ($numrecs-1)) ? 1 : 0;
smd_tag_populate($row);
$out[$index] = ($thing) ? parse($thing) : (($form) ? parse_form($form) :
str_repeat(