Wiki source for LinkManager


Show raw source

=====A Robust Link Management Solution=====

The system currently:

~A) Detects whenever a page mentions its name
~A) Detects whenever a page stops mentioning its name
~A) Allows admins to create "link groups"
~A) Allows managers to move links between "link groups" with ease
~A) Allows managers to assign weights to individual links
~A) Renders "link groups" in a variety of styles
~A) Allows content of linked pages to be embedded within the calling page

====The Data Model====

''you may have to replace wikka_ with your used prefix'' --NilsLindenberg

%%(mysql)
CREATE TABLE `wikka_link_manager_groups` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(100) NOT NULL default '',
`description` mediumtext,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) TYPE=MyISAM AUTO_INCREMENT=1 ;

CREATE TABLE `wikka_link_manager_links` (
`link_id` int(11) NOT NULL auto_increment,
`this_tag` varchar(75) NOT NULL default '',
`page_tag` varchar(75) NOT NULL default '',
`link_group_id` int(11) default NULL,
`weight` int(11) default NULL,
PRIMARY KEY (`link_id`)
) TYPE=MyISAM AUTO_INCREMENT=1 ;
%%

====The PHP Code====

I've found that it's convenient to keep a "common settings" file in the actions directory to store variables shared between actions and handlers. In my setup, I call it "common.ini.php". Here is the code:

%%(php)
<?php
// all
$base_url = 'http://localhost/wikka/wikka.php';
$page_var = 'wakka';

// linkmanager.php
$table_link_groups = $this->config['table_prefix'] . 'link_manager_groups';
$table_links = $this->config['table_prefix'] . 'link_manager_links';
$table_pages = $this->config['table_prefix'] . 'pages';
?>
%%

Next up is the "linkmanager.php" file, which should also be saved in the actions directory...

%%(php)
<?php

/**
* admin interface
* {{linkmanager}}
*
* display options
* {{lmshow}} -- (default) returns all groups as definition list (dl) of links (using the default "box" style)
* {{lmshow style="CSS STYLE"}} -- replace CSS STYLE with box, event, or table for different looks
* {{lmshow group="Group One"}} -- show the group labeled "Group One" as dl of links
* {{lmshow group="Group One" embed="all"}} -- embed the entire body of every link in this page
* {{lmshow group="Group One" embed="Overview"}} -- embed section with heading "Overview" of every link in this page
*/

include('actions/common.ini.php');

$is_admin = $this->IsAdmin();
$has_access = $this->HasAccess('write');

if ($has_access)
{
if (isset($_POST['update_links']))
{
mysql_query("DELETE FROM $table_links WHERE this_tag = '$this->tag'");
$links_to_move = isset($_POST['selected_links']) ? $_POST['selected_links'] : array();
if (isset($_POST['link_weights']))
{
foreach ($_POST['link_weights'] as $page_tag => $link_weight)
{
if (empty($link_weight)) $link_weight = 'NULL';
if (isset($links_to_move[$page_tag]))
{
$link_group_id = $_POST['group_select'];
unset($links_to_move[$page_tag]);
}
else
{
$link_group_id = isset($_POST['link_group_ids']) ? $_POST['link_group_ids'][$page_tag] : '';
}
mysql_query("INSERT INTO $table_links (this_tag, page_tag, link_group_id, weight) VALUES ('$this->tag', '$page_tag', $link_group_id, $link_weight)");
}
}
if (count($links_to_move) && $_POST['group_select'] != 'None')
{
foreach ($links_to_move as $page_tag => $junk)
{
mysql_query("INSERT INTO $table_links (this_tag, page_tag, link_group_id) VALUES ('$this->tag', '$page_tag', $_POST[group_select])");
}
}
}
}

if ($is_admin)
{
if (isset($_GET['delete_group']))
{
mysql_query("DELETE FROM $table_link_groups WHERE id = $_GET[group_id]");
}

if (isset($_POST['add_new_group']))
{
$new_group_name = mysql_real_escape_string($_POST['group_name']);
$new_group_description = mysql_real_escape_string($_POST['group_description']);
mysql_query("INSERT INTO $table_link_groups (name, description) VALUES ('$new_group_name', '$new_group_description')");
}

if (isset($_POST['update_groups']))
{
$group_names = $_POST['group_names'];
$group_descriptions = $_POST['group_descriptions'];
foreach ($group_names as $group_id => $group_name)
{
$group_description = mysql_real_escape_string($group_descriptions[$group_id]);
mysql_query("UPDATE $table_link_groups SET name = '$group_name', description = '$group_description' WHERE id = $group_id");
}
}
}

if ($has_access)
{
echo "<form id=\"add_link_group\" action=\"$base_url\" method=\"post\">";
echo "<input type=\"hidden\" name=\"$page_var\" value=\"$this->tag\">";
if ($is_admin)
{
?>
<h3 style="border-bottom: 1px solid #aaa;">Manage Link Groups</h3>
<div style="background: #fafafa">
<div style="background: #eef"><table cellpadding="5" id="add_link_group">
<tr><th>Name</th><th>Description</th></tr>
<tr>
<td valign="top"><input type="text" name="group_name" value=""></td>
<td><textarea cols="40" rows="2" name="group_description"></textarea></td>
</tr>
<tr><td colspan="3" align="right"><input type="submit" name="add_new_group" value="Add Group"></td></tr>
</table></div>
<?php
}
$sql = "SELECT id, name, description FROM $table_link_groups ORDER BY id DESC";
$result = mysql_query($sql);
if ($result && mysql_num_rows($result))
{
$group_select = '<select name="group_select"><option value="none">None</option>';
$manage_groups = '<table cellpadding="5" id="edit_link_groups"><tr><th>Name</th><th>Description</th><th align="center">Actions</th></tr>';
while ($row = mysql_fetch_array($result))
{
$groups[$row['id']] = $row['name'];
$group_select .= "<option value=\"$row[id]\">$row[name]</option>";
$delete = "<a href=\"$base_url?$page_var=$this->tag&delete_group=1&group_id=$row[id]\">Delete</a>";
$manage_groups .= "<tr><td valign=\"top\"><input type=\"text\" name=\"group_names[$row[id]]\" value=\"$row[name]\"></td>";
$manage_groups .= "<td><textarea name=\"group_descriptions[$row[id]]\" cols=\"40\" rows=\"2\">$row[description]</textarea></td><td align=\"center\" valign=\"top\">$delete</td></tr>";
}
$manage_groups .= '<tr><td colspan="2" align="right"><input type="submit" name="update_groups" value="Update Groups"></td><td> </td></tr>';
$manage_groups .= '</table></div>';
$group_select .= '</select>';
}
if ($is_admin) echo $manage_groups;
?>
<h3 style="border-bottom: 1px solid #aaa;">Manage Links</h3>
<?php
$sql = "SELECT link_id, tag, link_group_id, weight FROM $table_pages LEFT JOIN $table_links ON tag = page_tag WHERE MATCH (body) AGAINST ('$this->tag') AND latest = 'Y' AND tag != '$this->tag' ORDER BY link_group_id DESC, weight DESC";
$result = mysql_query($sql);
if ($result && mysql_num_rows($result))
{
echo '<table width="500" cellpadding="5" id="edit_links"><tr><th> </th><th>Page Tag</th><th align="center">Group</th><th align="center">Weight</th></tr>';
while ($row = mysql_fetch_array($result))
{
$group_valid = isset($groups[$row['link_group_id']]);
if ($group_valid) $valid_links[] = $row['link_id'];
$color = (++$i % 2 != 0) ? 'bgcolor="#f6f6f6"' : '';
$group_name = $group_valid && $row['link_group_id'] ? $groups[$row['link_group_id']] . "<input type=\"hidden\" name=\"link_group_ids[$row[tag]]\" value=\"$row[link_group_id]\">" : 'None';
$group_weight = $group_valid && $row['link_id'] ? "<input size=\"3\" type=\"text\" name=\"link_weights[$row[tag]]\" value=\"$row[weight]\">" : 'None';
echo "<tr $color><td><input type=\"checkbox\" name=\"selected_links[$row[tag]]\"></td>";
echo "<td><a href=\"$base_url?$page_var=$row[tag]\">$row[tag]</a></td>";
echo "<td align=\"center\">$group_name</td>";
echo "<td align=\"center\">$group_weight</td></tr>";
}
echo "<tr><td align=\"right\" colspan=\"4\"><b>move selected links to:</b> $group_select ";
echo '<input type="submit" name="update_links" value="Update Links"></td></tr>';
echo '</table>';
// remove links that have been unlinked in the source document
$valid_links = join(',', $valid_links);
mysql_query("DELETE FROM $table_links WHERE this_tag = '$this->tag' AND link_id NOT IN ($valid_links)");
}
else
{
echo '<p>There are no pages linking to this one.</p>';
}
echo '</form>';
}
?>
%%

Now, save the following code as "lmshow.php" in the same directory...

%%(php)
<?php

include('actions/common.ini.php');

if (!function_exists('smart_title'))
{
function smart_title($wikka_body)
{
return preg_match('/(=){2,5}([^=]*)(=){2,5}/', $wikka_body, $matches) ? $matches[2] : '';
}
}
if (!function_exists('fetch_section'))
{
function fetch_section($wikka_body, $section)
{
return preg_match("/(=){2,5}$section(=){2,5}([^=]*)/ism", $wikka_body, $matches) ? $matches[3] : false;
}
}

$group = isset($vars['group']) ? $vars['group'] : '__ALL__';
$embed = isset($vars['embed']) ? $vars['embed'] : false;
$css_class = isset($vars['style']) ? $vars['style'] : 'box';

$group_where = $group == '__ALL__' ? '' : "WHERE name = '$group'";
$link_where = '';

$sql = "SELECT id, name, description FROM $table_link_groups $group_where";
$result = mysql_query($sql);
if ($result && mysql_num_rows($result))
{
while ($row = mysql_fetch_array($result))
{
if (strtolower($row['name']) == strtolower($group)) $link_where = "AND link_group_id = $row[id]";
$groups[$row['id']]['name'] = $row['name'];
$groups[$row['id']]['description'] = $row['description'];
$count[$row['id']] = ceil(strlen($row['description']) / 25);
}
}
else
{
return false;
}

$sql = "SELECT link_group_id, page_tag, body FROM $table_links l, $table_pages p WHERE l.page_tag = p.tag AND p.latest ='Y' AND this_tag = '$this->tag' $link_where ORDER BY weight DESC";
$result = mysql_query($sql);
if ($result && mysql_num_rows($result))
{
while ($row = mysql_fetch_array($result))
{
$groups[$row['link_group_id']]['links'][$row['page_tag']] = $row['body'];
++$count[$row['link_group_id']];
}
arsort($count);
foreach ($count as $id => $n)
{
if (is_array($groups[$id]['links']))
{
$name = $groups[$id]['name'];
$css_id = str_replace(' ', '_', $name);
if ($embed)
{
foreach ($groups[$id]['links'] as $page_tag => $body)
{
$body = preg_replace('/{{.*linkmanager.*}}/U', '', $body);
$body = preg_replace('/{{.*lmshow.*}}/U', '', $body);
$output .= ($embed == 'all') ? trim($body) : fetch_section($body, $embed);
$output .= "\n\n";
}
echo $this->Format($output);
}
else
{
echo "<dl class=\"lm_$css_class\" id=\"lm_$css_id\">";
echo "<dt>$name</dt>";
if ($summary = $groups[$id]['description']) echo "<dd class=\"summary\">$summary</dd>";
foreach ($groups[$id]['links'] as $page_tag => $body)
{
$title = empty($body) ? '' : 'title="' . smart_title($body) . '"';
echo "<dd><a href=\"$base_url?$page_var=$page_tag\" $title>$page_tag</a></dd>";
}
echo '</dl>';
}
}
}
}
?>
%%

====Required CSS Definitions====

Add this to the end of your wikka.css file...

%%(css)
dl.lm_box
{
float: left;
width: 176px;
padding: 4px 0px 4px 2px;
background: #F1F1F1;
border: 1px solid #999;
border-left: 1px solid #999;
margin: 10px;
margin-bottom: 20px;
text-align: left;
}

dl.lm_box dt {
border: solid 1px #ccc;
background: #e6e6e6;
font-weight: bold;
padding: 2px;
}

dl.lm_box dd.summary {
padding: 5px;
margin: 2px 2px 0px 0px;
background: #fafafa;
}

dl.lm_box dd {
padding: 0px;
margin: 2px 2px 0px 0px;
}

dl.lm_box dd a
{
display: block;
border: solid 1px #e6e6e6;
padding: 2px 6px 2px 6px;
margin: 0px 2px;
background: #e6e6e6;
height: 1%;
line-height: 1em;
color: #000;
}

dl.lm_box dd a:hover
{
border: solid 1px #999;
background: #CCC;
}

dl.lm_box dd a:hover, dl.lm_box dd a:visited, dl.lm_box dd a:active, dl.lm_box dd a:link
{
text-decoration: none ! important;
}

dl.lm_event
{
margin: 0;
padding: 0;
font-family: georgia, times, serif;
}

.lm_event dt
{
position: relative;
left: 0;
top: 1.5em;
width: 15em;
font-weight: bold;
}

.lm_event dd
{
border-left: 1px solid #000;
margin: 0 0 0 12em;
padding: 0 0 .5em 1.5em;
}

dl.lm_table { border-top: 1px solid #ddd; border-bottom: 1px solid #ddd;}

.lm_table dt
{
width: 15em;
padding: .5em;
float: left;
margin: 0;
font-weight: bold;
}

.lm_table dd
{
margin-left: 16em;
padding: .5em;
}
%%

Lastly, you may want to save the following code as "clearfloats.php" in the actions directory.

%%(php)
<?php
echo '<div style="margin-bottom:-4em;clear:both;"> </div>';
?>
%%

====The Much Needed Example====

This is taken directly from my test setup, which can be seen in the screenshot below.

%%
{{linkmanager}}

====Using the lmshow Action====

{{lmshow}}
{{clearfloats}}

And some more text goes here...
%%

====The Screenshot====

{{image alt="Screenshot of the LinkManager action" url="http://bytebrite.com/img/lmss.gif"}}

I actually moused over the link at the bottom to show that link titles are automatically extracted from the linked pages and inserted into each anchor tag's title attribute, but I guess the old ""PrtScrn"" button didn't like that idea too much!


====But Wait... There's More!====

The action can do a lot more than display links. All of the display options are given at the top of linkmanager.php, but I'll repeat them here for easy reference.

%%
{{lmshow}} -- (default) returns all groups as definition list (dl) of links (using the default "box" style)
{{lmshow style="CSS STYLE"}} -- replace CSS STYLE with box, event, or table for different looks
{{lmshow group="Group One"}} -- show the group labeled "Group One" as dl of links
{{lmshow group="Group One" embed="all"}} -- embed the entire body of every link in this page
{{lmshow group="Group One" embed="Overview"}} -- embed section with heading "Overview" of every link in this page
%%

====References and Places I Stole From====

~-[[http://www.maxdesign.com.au/presentation/definition/ | Definition lists - misused or misunderstood?]]
~-[[http://www.xdevdesign.com/Better_MS_Menu.htm | An MS Sidebar with Better Semantics and Standards]]

====Todo List/Discussion Topics====

~- Should we incorporate per-link group "Show to this User/Usergroup List" which will allow for certain link groups to be displayed to certain audiences? If so, we must allow for duplication of links across link groups.
~~- Would it make more sense to apply the user/usergroup list on a per-link level? This would solve the problem of having to duplicate links across groups, but may necessitate more work for the link manager. The logic is that each link may be useful to any number of audiences and chances are the same label will be used to group the links regardless of the audience. If we applied the user list at the level of the link group, we would have to change the labels slightly to accomodate for each group (or add a name field to the DB and rework the code a bit). We can avoid this hassle by applying "show to so-and-so instructions" on a per-link level.
~- Allow attachments and referrers to be added to link groups as well.

====Authors====

DennyShimkoski

----
CategoryUserContributions
Valid XHTML :: Valid CSS: :: Powered by WikkaWiki