Revision history for BookmarkManager


Revision [23477]

Last edited on 2016-05-20 07:38:48 by BrianKoontz [Replaces old-style internal links with new pipe-split links.]
Additions:
<<This page has been **deprecated**, as this application has been repackaged as a plugin for Wikka 1.1.6.6. ++Please visit the new [[http://www.wikkacase.org/wiki/BookmarkMgr | BookmarkMgr page]] for updated information.++ WikkaCase is down indefinitely. This is the latest info on BookmarkManager. Also, please note the Freetag project has mysteriously disappeared from Google Code and has shown up [[https://github.com/erictchiu/freetag | here]].<<::c::
An integrated bookmark manager/[[http://en.wikipedia.org/wiki/Social_bookmarking | social bookmarking]] framework in the spirit of [[de.lirio.us]]. //Note:// Scroll down to the import section for scripts to import de.lirio.us data and other bookmarks into the BookmarkManager.
- Gordon Luk (creator of [[http://getluky.net/freetag/ | Freetag]], the engine behind Bookmark Manager) has some interesting comments about tagging [[http://tagsonomy.com/index.php/interview-with-gordon-luk-freetag/ | here]].
- Here's another [[http://www.integratedelf.com/wiki/doku.php?id=wikkawiki:about | Wikka tagging project]] using the [[http://getluky.net/freetag | Freetag]] engine. It would be useful to investigate the merging of that project with this one to permit page tagging in addition to bookmark tagging.
- **Beta test announcement:** Well, after hacking around with Scuttle, I decided it was much too complex for what I had in mind. Nils suggested another project, [[http://getluky.net/freetag/ | Freetag]], that seems to provide the basic functionality needed without all of the formatting/presentation overhead. I think something like this would be a cool addition (as a 100% modular plugin) to Wikka...
- ++[[http://wikkawiki.org/BookmarkManager?show_comments=1&showall=1#comment_4505 | Move markup to their own div tags]]++
- ++[[http://wikkawiki.org/BookmarkManager?show_comments=1&showall=1#comment_4507 | Move helper functions, class extensions to libs/ dir]]++
- Install a copy of [[http://code.google.com/p/freetag/ | Freetag]] in **3rdparty/plugins/** (version 0.250 or better) //Note: Rename the installation directory (Freetag-0.250) to 'freetag'//
- In order to use the stylesheet, add the following lines to the files indicated (click [[http://wush.net/trac/wikka/ticket/246 | here]] for a justification as to why these changes are necessary to the core):
- [[BookmarkManagerImportScripts | Import scripts]]
Deletions:
<<This page has been **deprecated**, as this application has been repackaged as a plugin for Wikka 1.1.6.6. ++Please visit the new [[http://www.wikkacase.org/wiki/BookmarkMgr BookmarkMgr page]] for updated information.++ WikkaCase is down indefinitely. This is the latest info on BookmarkManager. Also, please note the Freetag project has mysteriously disappeared from Google Code and has shown up [[https://github.com/erictchiu/freetag here]].<<::c::
An integrated bookmark manager/[[http://en.wikipedia.org/wiki/Social_bookmarking social bookmarking]] framework in the spirit of [[de.lirio.us]]. //Note:// Scroll down to the import section for scripts to import de.lirio.us data and other bookmarks into the BookmarkManager.
- Gordon Luk (creator of [[http://getluky.net/freetag/ Freetag]], the engine behind Bookmark Manager) has some interesting comments about tagging [[http://tagsonomy.com/index.php/interview-with-gordon-luk-freetag/ here]].
- Here's another [[http://www.integratedelf.com/wiki/doku.php?id=wikkawiki:about Wikka tagging project]] using the [[http://getluky.net/freetag Freetag]] engine. It would be useful to investigate the merging of that project with this one to permit page tagging in addition to bookmark tagging.
- **Beta test announcement:** Well, after hacking around with Scuttle, I decided it was much too complex for what I had in mind. Nils suggested another project, [[http://getluky.net/freetag/ Freetag]], that seems to provide the basic functionality needed without all of the formatting/presentation overhead. I think something like this would be a cool addition (as a 100% modular plugin) to Wikka...
- ++[[http://wikkawiki.org/BookmarkManager?show_comments=1&showall=1#comment_4505 Move markup to their own div tags]]++
- ++[[http://wikkawiki.org/BookmarkManager?show_comments=1&showall=1#comment_4507 Move helper functions, class extensions to libs/ dir]]++
- Install a copy of [[http://code.google.com/p/freetag/ Freetag]] in **3rdparty/plugins/** (version 0.250 or better) //Note: Rename the installation directory (Freetag-0.250) to 'freetag'//
- In order to use the stylesheet, add the following lines to the files indicated (click [[http://wush.net/trac/wikka/ticket/246 here]] for a justification as to why these changes are necessary to the core):
- [[BookmarkManagerImportScripts Import scripts]]


Revision [21381]

Edited on 2011-03-16 19:53:31 by BrianKoontz [updates]
Additions:
<<This page has been **deprecated**, as this application has been repackaged as a plugin for Wikka 1.1.6.6. ++Please visit the new [[http://www.wikkacase.org/wiki/BookmarkMgr BookmarkMgr page]] for updated information.++ WikkaCase is down indefinitely. This is the latest info on BookmarkManager. Also, please note the Freetag project has mysteriously disappeared from Google Code and has shown up [[https://github.com/erictchiu/freetag here]].<<::c::
Deletions:
<<This page has been **deprecated**, as this application has been repackaged as a plugin for Wikka 1.1.6.6. Please visit the new [[http://www.wikkacase.org/wiki/BookmarkMgr BookmarkMgr page]] for updated information.<<::c::


Revision [20234]

Edited on 2008-09-09 22:28:46 by BrianKoontz [deprecated]
Additions:
<<This page has been **deprecated**, as this application has been repackaged as a plugin for Wikka 1.1.6.6. Please visit the new [[http://www.wikkacase.org/wiki/BookmarkMgr BookmarkMgr page]] for updated information.<<::c::


Revision [19476]

Edited on 2008-01-28 00:16:36 by BrianKoontz [Modified links pointing to docs server]

No Differences

Revision [17932]

Edited on 2008-01-26 18:10:41 by BrianKoontz [Removed demo link]
Additions:
An integrated bookmark manager/[[http://en.wikipedia.org/wiki/Social_bookmarking social bookmarking]] framework in the spirit of [[de.lirio.us]]. //Note:// Scroll down to the import section for scripts to import de.lirio.us data and other bookmarks into the BookmarkManager.
- **Beta test announcement:** Well, after hacking around with Scuttle, I decided it was much too complex for what I had in mind. Nils suggested another project, [[http://getluky.net/freetag/ Freetag]], that seems to provide the basic functionality needed without all of the formatting/presentation overhead. I think something like this would be a cool addition (as a 100% modular plugin) to Wikka...
Deletions:
An integrated bookmark manager/[[http://en.wikipedia.org/wiki/Social_bookmarking social bookmarking]] framework in the spirit of [[de.lirio.us]]. //Note:// Scroll down to the import section for scripts to import de.lirio.us data and other bookmarks into the BookmarkManager.
- **Beta test announcement:** Well, after hacking around with Scuttle, I decided it was much too complex for what I had in mind. Nils suggested another project, [[http://getluky.net/freetag/ Freetag]], that seems to provide the basic functionality needed without all of the formatting/presentation overhead. I have a beta up and running [[http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks here]], and would appreciate your comments and suggestions! Keep in mind this is still quite rough around the edges, but I was aiming more towards proof-of-concept. I think something like this would be a cool addition (as a 100% modular plugin) to Wikka...


Revision [17931]

Edited on 2008-01-26 18:08:56 by BrianKoontz [Removed demo links]
Additions:
print "<div class=\"bookmark_box\"><div class=\"bookmark_desc\">".($obj['description'] ? $obj['description'] : " ")."</div>\n";
//print "</div><div class=\"clear\"> </div><hr/>";
print $prev_url." ".$next_url;
print "<div class=\"bookmark_box\"><div class=\"bookmark_desc\">".($obj['description'] ? $obj['description'] : " ")."</div>\n";
//print "<div class=\"clear\"> </div><hr/>";
print $prev_url." ".$next_url;
print $prev_url." ".$next_url;
// print "<div class=\"clear\"> </div>";
Deletions:
<<Try it out! Beta version with latest code [[http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks here]].
- If you want to test bookmarklets, copy and paste this as a bookmark:
%% javascript:location.href='http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks&action=add&uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&wikka_bookmarklet=1'
then go to a webpage and click on the bookmark you just saved.
<<::c::
print "<div class=\"bookmark_box\"><div class=\"bookmark_desc\">".($obj['description'] ? $obj['description'] : " ")."</div>\n";
//print "</div><div class=\"clear\"> </div><hr/>";
print $prev_url."  ".$next_url;
print "<div class=\"bookmark_box\"><div class=\"bookmark_desc\">".($obj['description'] ? $obj['description'] : " ")."</div>\n";
//print "<div class=\"clear\"> </div><hr/>";
print $prev_url."  ".$next_url;
print $prev_url."  ".$next_url;
// print "<div class=\"clear\"> </div>";


Revision [17444]

Edited on 2007-08-23 18:32:30 by DarTar [reverted]
Additions:
%% javascript:location.href='http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks&action=add&uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&wikka_bookmarklet=1'
%%
then go to a webpage and click on the bookmark you just saved.
<<::c::
==Screenshots==
**Main page**
{{image class="left" alt="BookmarkManager main page" url="http://durango.dcccd.edu/images/ss1.jpg"}}
::c::
**Add bookmark page**
{{image class="left" alt="BookmarkManager add page" url="http://durango.dcccd.edu/images/ss2.jpg"}}
::c::
==Design ideas==
- Allow both user-specific and site-wide bookmark access (other than those tagged as "private")
- Allow tagging of objects other than URIs (such as pages)
- Implement as both a handler and an action
- Gordon Luk (creator of [[http://getluky.net/freetag/ Freetag]], the engine behind Bookmark Manager) has some interesting comments about tagging [[http://tagsonomy.com/index.php/interview-with-gordon-luk-freetag/ here]].
- Here's another [[http://www.integratedelf.com/wiki/doku.php?id=wikkawiki:about Wikka tagging project]] using the [[http://getluky.net/freetag Freetag]] engine. It would be useful to investigate the merging of that project with this one to permit page tagging in addition to bookmark tagging.
==Progress reports==
- **Beta test announcement:** Well, after hacking around with Scuttle, I decided it was much too complex for what I had in mind. Nils suggested another project, [[http://getluky.net/freetag/ Freetag]], that seems to provide the basic functionality needed without all of the formatting/presentation overhead. I have a beta up and running [[http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks here]], and would appreciate your comments and suggestions! Keep in mind this is still quite rough around the edges, but I was aiming more towards proof-of-concept. I think something like this would be a cool addition (as a 100% modular plugin) to Wikka...
- Progress is going well. Add/edit/delete capabilities have been added. Two Perl scripts (see below) have been provided that allow you to export your bookmarks from de.lirio.us and import them into BookmarkManager. Beta currently running under Wikka 1.1.6.2-alpha. A page of 25 links (out of 600+) and 100 tags (out of 1200+) renders in approximately 1.1 seconds. A tag seach of 1207 tags takes approximately 1.2 seconds.
- More enhancements and modifications: Numeric tags, link pagination, tag pagination, tag/link editing and deletion.
- More cool stuff: Support for bookmarklets (small Javascript bookmark that immediately saves the page you are viewing and returns to the page), tag links within link descriptors, better use of screen real estate using CSS.
- Tags are now searchable! Searchable titles/descriptions to come...
==TODO==
++Indicates issues that have been addressed++
- ++This code is most likely not yet safe for a production environment!++ //I've worked to secure all data passed in from GET/POST requests, as well as from Wikka itself in some cases. I'm hoping a few more folks have time to look over the code for any obvious security weaknesses.//
- User tags should be displayed as raw tags.
- As a general consideration it would be nice to provide this functionality as a handler.
- ++it would be nice to have a link for the add-form near list yours.++
- ++how about something like my tags / all tags?++ //List all/list yours now provides this functionality//
- what would be usefull: If I tag a link with the name of an existing wiki-page, this link could show up at the end of the page.
- ++Need to fix logic so "your" tags only display "your" links (also, identify tag cloud as "all" or "yours")++
- Optional logic to display tags in a right-justified vertical list instead of a cloud
- ++Paging++, user prefs
- Tag pages as well as links
- ++Please put the wikka_freetag class in a file like Wikka.freetag.class.php in the new \libs directory.++
- ++$freetag_options -> why not use the params provided by wikka ($this->mysql_host, etc.)?++
- ++Create bookmarklets such as these (borrowed from [[de.lirio.us]])++: //Implemented...see below for details//
- ++Provide edit/delete functionality++
- Search functionality (multi-tag, by title/desc)
- Restricting spam. Some ideas: ++Option to allow only registered users to set bookmarks++ //Implemented//; using "captcha" images to preclude automated scripts
- ++It would be nice if tags in each bookmarks were links pointing to the list of bookmarks with the same tag.++
- ++[[http://wikkawiki.org/BookmarkManager?show_comments=1&showall=1#comment_4505 Move markup to their own div tags]]++
- ++[[http://wikkawiki.org/BookmarkManager?show_comments=1&showall=1#comment_4507 Move helper functions, class extensions to libs/ dir]]++
- Fix PHP warnings w/r/t/ null arrays in ksort, min, max, etc.
- The codebase is getting large enough to consider distributing as a tarball/zipfile.
- Split processing of tags and links so they are independent of each other (AJAX??)
- Check/warn on link dupes during add new link operation
- Title and description: What's the diff? More useful to display URL in lieu of one of these?
- Export options: RSS, all to file, to screen (suitable for copy-and-paste), etc.
==Installation==
- Install a copy of [[http://code.google.com/p/freetag/ Freetag]] in **3rdparty/plugins/** (version 0.250 or better) //Note: Rename the installation directory (Freetag-0.250) to 'freetag'//
- Create a MySQL database (I used ##freetag## as the DB name) and use the MySQL dump below to create the necessary tables (such as ##mysql -u root freetag < wikka_freetag.sql##):
**wikka_freetag.sql**
%%(sql)
-- $Id: freetag.sql,v 1.2 2006/05/29 17:11:44 brian Exp brian $
--
-- MySQL dump 9.11
-- Freetag Structure v2.02
--
-- Table structure for table `freetags`
--
DROP TABLE IF EXISTS freetags;
CREATE TABLE freetags (
id int(10) unsigned NOT NULL auto_increment,
tag varchar(30) NOT NULL default '',
raw_tag varchar(50) NOT NULL default '',
PRIMARY KEY (id)
) TYPE=MyISAM;
--
-- Table structure for table `freetagged_objects`
--
DROP TABLE IF EXISTS freetagged_objects;
CREATE TABLE freetagged_objects (
tag_id int(10) unsigned NOT NULL default '0',
tagger_id int(10) unsigned NOT NULL default '0',
object_id int(10) unsigned NOT NULL default '0',
tagged_on datetime NOT NULL default '0000-00-00 00:00:00',
PRIMARY KEY (`tag_id`,`tagger_id`,`object_id`),
KEY `tag_id_index` (`tag_id`),
KEY `tagger_id_index` (`tagger_id`),
KEY `object_id_index` (`object_id`)
) TYPE=MyISAM;
--
-- Table structure for table `freetag_bookmarks`
--
DROP TABLE IF EXISTS freetag_bookmarks;
CREATE TABLE freetag_bookmarks (
bookmark_id int(10) unsigned NOT NULL auto_increment,
title varchar(255) NOT NULL default '',
uri varchar(255) NOT NULL,
description varchar(255) default NULL,
wikka_id varchar(75) NOT NULL,
private tinyint(1) NOT NULL default '0',
PRIMARY KEY (`bookmark_id`),
KEY `bookmark_id_index` (`bookmark_id`)
) TYPE=MyISAM;
--
-- Table structure for table `freetag_wikka_id_map`
--
DROP TABLE IF EXISTS freetag_wikka_id_map;
CREATE TABLE freetag_wikka_id_map (
tagger_id int(10) unsigned NOT NULL AUTO_INCREMENT,
wikka_id varchar(75) NOT NULL default '',
PRIMARY KEY(`tagger_id`)
) TYPE=MyISAM;
%%
- Save the following file as ##actions/bookmarks.php##:
**bookmarks.php**
%%(php)
<?php
/**
* Bookmark manager with tagging
*
* Allows users (registered or not) to tag objects such as
* bookmarks and display linked tags in the spirit of
* {@link http://de.licio.us} and {@link http://de.lirio.us}.
*
* Usage: {{bookmarks}}
*
* Credits:
* Gordon Luk's Freetag for PHP4 {@link http://www.getluky.net/freetag}
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public Licence for more details:
*
* http://www.gnu.org/copyleft/gpl.html
*
*
* @author {@link http://wikkawiki.org/BrianKoontz Brian Koontz} <brian@pongonova.net>
* @copyright Copyright (c) 2006, Brian Koontz <brian@pongonova.net>
* @name BookmarkManager
* @package Actions
* @filesource
* @license http://www.gnu.org/copyleft/gpl.html
* @since Wikka 1.1.6.1
* @version $Id: bookmarks.php,v 1.8 2006/08/10 04:03:38 brian Exp brian $
*
*/
// Check for freetag class and helper libraries
include_once('libs/Wikka.freetag.class.php');
include_once('libs/Wikka.freetag.lib.php');
/********************************************************************/
/* DB connection params */
/********************************************************************/
$freetag_options = array(
'db_user' => $this->config['mysql_user'],
'db_pass' => $this->config['mysql_password'],
'db_host' => $this->config['mysql_host'],
'db_name' => 'freetag',
'PCONNECT' => true,
);
$freetag = new wikka_freetag($freetag_options);
$wikka_id = $this->GetUserName();
$tagger_id = wikka_id_to_tagger_id($wikka_id, $this, $freetag_options);
$all_obj_count = count_objects(NULL, $this, $freetag_options);
$your_obj_count = count_objects($wikka_id, $this, $freetag_options);
$search_type = "";
if(isset($_REQUEST['action'])) {
if($_REQUEST['action']=="list_yours") {
$search_type = "search_yours";
} else {
$search_type = "search_all";
}
}
echo "<a href=\"".$this->href()."&action=list_all"."\">List all</a> (".$all_obj_count.") | ".
"<a href=\"".$this->href()."&action=list_yours"."\">List yours</a> (".$your_obj_count.") | ".
"<a href=\"".$this->href()."&action=add"."\">Add</a>".
$this->FormOpen().
"<input type=\"hidden\" name=\"action\" value=\"".$search_type."\" />".
"Search: ".
"<input name=\"search\" size=\"20\" />".
"<input type=\"radio\" name=\"search_type\" value=\"tags\" checked>Tags".
"<input type=\"radio\" name=\"search_type\" value=\"title_desc\">Title/Desc".
$this->FormClose();
if(!isset($_REQUEST['action'])) {
// Display all tags at start of session
$this->Redirect($this->href()."&action=list_all");
}
// Search by tags, titles, or descriptions
// Action name: search
// Search field name: search
// Radio buttons: search_type: "tags" or "title_desc"
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="search_yours") || ($_REQUEST['action']=="search_all")) {
if(isset($_REQUEST['search_type']) &&
($_REQUEST['search_type']=="tags") &&
isset($_REQUEST['search'])) {
if($_REQUEST['action'] == "search_yours") {
$this->Redirect($this->href()."&search_term=".$_REQUEST['search']."&action=list_yours");
} else {
$this->Redirect($this->href()."&search_term=".$_REQUEST['search']."&action=list_all");
}
}
}
// Add new entry to DB
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="add")) {
$values = array();
// Check for request from a bookmarklet...check to see if user
// is logged in...if not, redirect to login page and come back
// here when done
if(isset($_REQUEST['wikka_bookmarklet'])) {
$values['uri'] = mysql_real_escape_string($_REQUEST['uri']);
if(isset($_REQUEST['title'])) {
$values['title'] = mysql_real_escape_string($_REQUEST['title']);
}
$values['referrer'] = $_SERVER['HTTP_REFERER'];
$values['when_done'] = "add_link";
save_to_current_session($values);
//session_write_close();
if(!$this->GetUser()) {
$_SESSION['go_back'] = $this->Href(null, $this->MiniHref()."&action=add");
//session_write_close();
$this->Redirect($this->config['base_url']."UserSettings");
}
}
// The next clause checks to see if we're returning from a
// login request while trying to execute a bookmarklet
if(isset($_SESSION['when_done']) && ($_SESSION['when_done']=="add_link")) {
$values['uri'] = $_SESSION['uri'];
$values['title'] = $_SESSION['title'];
$_SESSION['when_done'] = "return";
//session_write_close();
}
if(trim($_REQUEST['tags']) != "") {
// Private bookmark?
$private = 0;
if(strpos(trim($_REQUEST['tags']), "@private") !== false)
$private = 1;

// Converts numeric tag "123" to "123_" to facilitate
// alphanumeric sorting (otherwise, PHP converts string to
// true integer).
$tags = preg_split('/\s+/', $_REQUEST['tags'], -1, PREG_SPLIT_NO_EMPTY);
$tags = preg_replace('/^([0-9]+)$/', "$1_", $tags);
$tags = implode(" ", $tags);
$this->Query("insert ".$freetag_options['db_name'].".freetag_bookmarks set ".
"title = '".mysql_real_escape_string($_REQUEST['title'])."', ".
"uri = '".mysql_real_escape_string($this->cleanUrl($_REQUEST['uri']))."', ".
"wikka_id = '".mysql_real_escape_string($wikka_id)."', ".
"private = '".$private."', ".
"description = '".mysql_real_escape_string($_REQUEST['desc'])."';");
$last_id = $this->LoadSingle("select last_insert_id();");
$freetag->tag_object($tagger_id, $last_id['last_insert_id()'], $tags);
// Check to see if it's time to return to the page from which
// the bookmarklet was called
if(isset($_SESSION['when_done']) && ($_SESSION['when_done']=="return")) {
if(isset($_SESSION['referrer'])) {
$referrer = $_SESSION['referrer'];
unset($_SESSION['go_back']);
unset($_SESSION['uri']);
unset($_SESSION['title']);
unset($_SESSION['referrer']);
unset($_SESSION['when_done']);
$this->Redirect($referrer);
}
}
// Otherwise, go back to tag/link list view
$this->Redirect($this->href()."&action=list_yours");
} else {
// Display add form
print($this->FormOpen());
?>
<input type="hidden" name="action" value="add" />
<table>
<tr>
<td align="right">Title:</td>
<td><input name="title" size="40" value="<?php echo $values['title']; ?>" /></td>
</tr>
<tr>
<td align="right">URI:</td> <td><input name="uri" size="40" value="<?php echo $values['uri']; ?>" /></td> </tr>
<tr>
<td align="right">Description:</td>
<td><input name="desc" size="40"/></td>
</tr>
<tr>
<td align="right"></td>
<td><?php echo $this->Format("Use blank space between tags, include \"@private\" for private bookmark"); ?></td>
</tr>
<tr>
<td align="right">Tags:</td>
<td><input name="tags" size="40"/></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Add" size="40" /></td>
</tr>
</table>
<?php
print($this->FormClose());
}
}
// Edit entry
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="edit") && (isset($_REQUEST['object_id']))) {
$object_id = mysql_real_escape_string($_REQUEST['object_id']);
$obj = $this->LoadSingle("select bookmark_id,title,uri,description,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$object_id.";");
// Is the logged-in user the owner?
if($wikka_id != $obj['wikka_id']) {
$this->Redirect($this->href()."&action=list_all");
}
if(isset($_REQUEST['modify']) && trim($_REQUEST['tags']) != "") {
// Delete all tags, then re-tag
$freetag->delete_all_object_tags_for_user($tagger_id,$object_id);
// Private bookmark?
$private = 0;
if(strpos(trim($_REQUEST['tags']), "@private") !== false)
$private = 1;
$this->Query("update ".$freetag_options['db_name'].".freetag_bookmarks set ".
"title = '".mysql_real_escape_string($_REQUEST['title'])."', ".
"uri = '".mysql_real_escape_string($this->cleanUrl($_REQUEST['uri']))."', ".
"wikka_id = '".mysql_real_escape_string($wikka_id)."', ".
"private = '".$private."', ".
"description = '".mysql_real_escape_string($_REQUEST['desc'])."' ".
"where bookmark_id = ".mysql_real_escape_string($object_id)." ".
"limit 1;");
$freetag->tag_object($tagger_id, $obj['bookmark_id'], $_REQUEST['tags']);
$this->Redirect($this->href()."&action=list_yours");
}
// Display add form
print($this->FormOpen());
?>
<input type="hidden" name="action" value="edit" />
<input type="hidden" name="modify" value="yes" />
<input type="hidden" name="object_id" value="<?php print $object_id ?>" />
<table>
<tr>
<td align="right">Title:</td>
<td><input name="title" size="40" value="<?php print $obj['title']; ?>"/></td>
</tr>
<tr>
<td align="right">URI:</td>
<td><input name="uri" size="40" value="<?php print $obj['uri']; ?>"/></td>
</tr>
<tr>
<td align="right">Description:</td>
<td><input name="desc" size="40" value="<?php print $obj['description']; ?>"/></td>
</tr>
<tr>
<td align="right"></td>
<td><?php echo $this->Format("Use blank space between tags, include \"@private\" for private bookmark"); ?></td>
</tr>
<tr>
<td align="right">Tags:</td>
<?php
$tags = $freetag->get_tags_on_object($object_id);
$taglist = "";
foreach($tags as $idx=>$res) {
$taglist .= trim($res['raw_tag'])." ";
}
?>
<td><input name="tags" size="40" value="<?php print $taglist?>"/></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Edit" size="40" /></td>
</tr>
</table>
<?php
print($this->FormClose());
}
// Delete entry
if(isset($_REQUEST['action']) && $_REQUEST['action']=="delete") {
$object_id = mysql_real_escape_string($_REQUEST['object_id']);
$obj = $this->LoadSingle("select bookmark_id,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".mysql_real_escape_string($object_id).";");
// Is the logged-in user the owner?
if($wikka_id != $obj['wikka_id']) {
$this->Redirect($this->href()."&action=list_all");
}
// Delete all tags first...
$freetag->delete_all_object_tags_for_user($tagger_id,$object_id);
// ...then delete object
$this->Query("delete from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".mysql_real_escape_string($object_id)." limit 1;");
$this->Redirect($this->href()."&action=list_yours");
}
// List all bookmarks (ordered by most recent)
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="list_all")) {
$offset = 0;
if(isset($_REQUEST['offset'])) {
$offset = mysql_real_escape_string($_REQUEST['offset']);
}
$limit = 25;
if(isset($_REQUEST['limit'])) {
$limit = mysql_real_escape_string($_REQUEST['limit']);
}
$tag_offset = 0;
if(isset($_REQUEST['tag_offset'])) {
$tag_offset = mysql_real_escape_string($_REQUEST['tag_offset']);
}
$tag_limit = 100;
if(isset($_REQUEST['tag_limit'])) {
$tag_limit = mysql_real_escape_string($_REQUEST['tag_limit']);
}
// Output tag cloud
$url_opts = $this->href()."&action=list_all";
$search_term = NULL;
$header = "All tags";
if(isset($_REQUEST['search_term'])) {
$search_term = mysql_real_escape_string($_REQUEST['search_term']);
$header = "All tags by search";
}
output_tag_cloud($freetag,$url_opts,NULL,$header,$tag_offset,$tag_limit,$search_term);
$ids = "";
$tag = "";
if(isset($_REQUEST['tag']) && $_REQUEST['tag'] != "") {
$ids = $freetag->get_most_recent_objects(NULL,$_REQUEST['tag'],$offset,$limit);
$tag = mysql_real_escape_string($_REQUEST['tag']);
} else {
$ids = $freetag->get_most_recent_objects(NULL,NULL,$offset,$limit);
}
foreach($ids as $key=>$val) {
$tags = $freetag->get_tags_on_object($val['object_id']);
$obj = $this->LoadSingle("select title,uri,description,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$val['object_id']." and private = 0;");
if(!isset($obj)) {
continue;
}
print "<div class=\"bookmark_block\">\n";
print "<div class=\"bookmark_title\"><a href=\"".$obj['uri']."\">".$obj['title']."</a></div>\n";
print "<div class=\"bookmark_box\"><div class=\"bookmark_desc\">".($obj['description'] ? $obj['description'] : " ")."</div>\n";
$taglist = "";
$link = $url_opts."&tag_offset=".$tag_offset."&tag_limit=".$tag_limit;
foreach($tags as $idx=>$res) {
$temp_tag = trim($res['tag']);
$taglist .= "<a href=\"".$link."&tag=".$temp_tag."\">".$temp_tag."</a> ";
}
print $taglist."by ".$obj['wikka_id']." (created: ".$val['tagged_on'].")\n";
//print "</div><div class=\"clear\"> </div><hr/>";
print "</div></div>\n";
}
// Display pagination links
$obj_count = 0;
$tag_url = "";
if($tag != "") {
$obj_count = count_tagged_objects(NULL, $tag, $this, $freetag_options, $freetag);
$tag_url = "&tag=".$tag;
} else {
$obj_count = count_objects(NULL, $this, $freetag_options);
}
$prev_offset = $offset - $limit;
$prev_url = "";
if($prev_offset < 0) {
$prev_offset = 0;
}
if($offset > 0) {
$prev_url = "<a href=\"".$this->href()."&action=list_all&offset=".$prev_offset."&limit=".$limit.$tag_url."\"><<</a>";
}
$next_offset = $offset + $limit;
$next_url = "";
if($next_offset < $obj_count - 1) {
$next_url = "<a href=\"".$this->href()."&action=list_all&offset=".$next_offset."&limit=".$limit.$tag_url."\">>></a>";
}
print "<div class=\"pagination\">";
print $prev_url."  ".$next_url;
print "</div>";
}
// List user bookmarks (ordered by most recent)
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="list_yours")) {
$offset = 0;
if(isset($_REQUEST['offset'])) {
$offset = mysql_real_escape_string($_REQUEST['offset']);
}
$limit = 25;
if(isset($_REQUEST['limit'])) {
$limit = mysql_real_escape_string($_REQUEST['limit']);
}
$tag_offset = 0;
if(isset($_REQUEST['tag_offset'])) {
$tag_offset = mysql_real_escape_string($_REQUEST['tag_offset']);
}
$tag_limit = 100;
if(isset($_REQUEST['tag_limit'])) {
$tag_limit = mysql_real_escape_string($_REQUEST['tag_limit']);
}
// Output tag cloud
$url_opts = $this->href()."&action=list_yours";
$search_term = NULL;
$header = "Your tags";
if(isset($_REQUEST['search_term'])) {
$search_term = mysql_real_escape_string($_REQUEST['search_term']);
$header = "Your tags by search";
}
output_tag_cloud($freetag,$url_opts,$tagger_id,$header,$tag_offset,$tag_limit,$search_term);
$ids = "";
$tag = "";
if(isset($_REQUEST['tag']) && $_REQUEST['tag'] != "") {
$ids = $freetag->get_most_recent_objects($tagger_id, $_REQUEST['tag'], $offset, $limit);
$tag = mysql_real_escape_string($_REQUEST['tag']);
} else {
$ids = $freetag->get_most_recent_objects($tagger_id, NULL, $offset, $limit);
}
foreach($ids as $key=>$val) {
$tags = $freetag->get_tags_on_object($val['object_id']);
$obj = $this->LoadSingle("select title,uri,description,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$val['object_id'].";");
print "<div class=\"bookmark_block\">\n";
print "<div class=\"bookmark_title\"><a href=\"".$obj['uri']."\">".$obj['title']."</a></div>\n";
print "<div class=\"bookmark_box\"><div class=\"bookmark_desc\">".($obj['description'] ? $obj['description'] : " ")."</div>\n";
print " <a href=\"".$this->href()."&action=edit&object_id=".$val['object_id']."\">edit</a>";
print "|";
print "<a href=\"".$this->href()."&action=delete&object_id=".$val['object_id']."\">delete</a><br/>";
$taglist = "";
$link = $url_opts."&tag_offset=".$tag_offset."&tag_limit=".$tag_limit;
foreach($tags as $idx=>$res) {
$normtag = trim($res['tag']);
$rawtag = trim($res['raw_tag']);
$taglist .= "<a href=\"".$link."&tag=".$normtag."\">".$rawtag."</a> ";
}
print $taglist."by ".$obj['wikka_id']." (created: ".$val['tagged_on'].")\n";
//print "<div class=\"clear\"> </div><hr/>";
print "</div></div>\n";
}
// Display pagination links
$obj_count = 0;
$tag_url = "";
if($tag != "") {
$obj_count = count_tagged_objects($wikka_id, $tag, $this, $freetag_options, $freetag);
$tag_url = "&tag=".$tag;
} else {
$obj_count = count_objects($wikka_id, $this, $freetag_options);
}
$prev_offset = $offset - $limit;
$prev_url = "";
if($prev_offset < 0) {
$prev_offset = 0;
}
if($offset > 0) {
$prev_url = "<a href=\"".$this->href()."&action=list_yours&offset=".$prev_offset."&limit=".$limit.$tag_url."\"><<</a>";
}
$next_offset = $offset + $limit;
$next_url = "";
if($next_offset < $obj_count - 1) {
$next_url = "<a href=\"".$this->href()."&action=list_yours&offset=".$next_offset."&limit=".$limit.$tag_url."\">>></a>";
}
print "<div class=\"center\">";
print $prev_url."  ".$next_url;
print "</div>";
}
?>
%%
- Save the following file as ##libs/Wikka.freetag.lib.php##:
**Wikka.freetag.lib.php**
%%(php)
<?php
/**
* Wikka.freetag.lib.php
*
* Set of helper classes for BookmarkManager. The reason these
* aren't included in the Freetag extension class
* (Wikka.freetag.class.php) is that Wikka functions defined
* outside the scope of the class aren't accessible from within
* the class, and most of these helper functions use both Freetag
* and Wikka functions.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public Licence for more details:
*
* http://www.gnu.org/copyleft/gpl.html
*
*
* @author {@link http://wikkawiki.org/BrianKoontz Brian Koontz} <brian@pongonova.net>
* @copyright Copyright (c) 2006, Brian Koontz <brian@pongonova.net>
* @name Wikka.freetag.lib.php
* @package Actions {@link http://www.wikkawiki.org/BookmarkManager}
* @filesource
* @license http://www.gnu.org/copyleft/gpl.html
* @since Wikka 1.1.6.1
* @version $Id: Wikka.freetag.lib.php,v 1.3 2006/08/10 04:03:17 brian Exp brian $
*
*/
/*********************************************************************/
/* Helper functions */
/*********************************************************************/
/**
* wikka_id_to_tagger_id()
*
* Converts Wikka id (returned from GetUserName()) to
* tagger_id. Adds Wikka id to table if not already present.
* Maps NULL -> NULL.
*
*/
function wikka_id_to_tagger_id ($wikka_id, $obj, $freetag_options) {
if(!isset($wikka_id)) {
return NULL;
}
$wikka_id = mysql_real_escape_string($wikka_id);
$res = $obj->LoadSingle("select tagger_id from ".$freetag_options['db_name'].".freetag_wikka_id_map where wikka_id = '".$wikka_id."';");
$tagger_id = $res['tagger_id'];
if(!$tagger_id) {
$obj->Query("insert ".$freetag_options['db_name'].".freetag_wikka_id_map set "."wikka_id = '".$wikka_id."';");
$res = $obj->LoadSingle("select last_insert_id();");
$tagger_id = $res['last_insert_id()'];
}
return $tagger_id;
}
/********************************************************************/
/**
* output_tag_cloud()
*
* Create a cloud of tags!
*
* NOTES:
* Set $tagger_id to NULL for all tags (except those marked
* private)
*
* If $search_term is not NULL, $tag_offset will return the offset
* of the first matching term
*/
function output_tag_cloud($freetag,$tag_page_url,$tagger_id=NULL,$header=NULL,$tag_offset=0,$tag_limit=100,$search_term=NULL) {
// Output tag cloud
$tag_count = $freetag->count_unique_tags($tagger_id,$search_term);
print "<div class='bookmark_floatr'>";
if($header) {
print $header." (".$tag_count."):<br/>";
}
print $freetag->get_tag_cloud_html_with_limits($tag_offset,$tag_limit,10,20,'px','cloud_tag',$tag_page_url,$tagger_id,$search_term);
// Display pagination links
$tag_url = "";
$prev_offset = $tag_offset - $tag_limit;
$prev_url = "";
if($prev_offset < 0) {
$prev_offset = 0;
}
$search_tag = "";
if(isset($search_term)) {
$search_tag = "&search_term=".$search_term;
}
if($tag_offset > 0) {
$prev_url = "<a href=\"".$tag_page_url."&tag_offset=".$prev_offset."&tag_limit=".$tag_limit.$search_tag."\"><<</a>";
}
$next_offset = $tag_offset + $tag_limit;
$next_url = "";
if($next_offset < $tag_count - 1) {
$next_url = "<a href=\"".$tag_page_url."&tag_offset=".$next_offset."&tag_limit=".$tag_limit.$search_tag.$tag_url."\">>></a>";
}
print "<div class=\"pagination\">";
print $prev_url."  ".$next_url;
print "</div>";
print "</div>";
// print "<div class=\"clear\"> </div>";
}
/********************************************************************/
/**
* count_objects()
*
* Returns a count of objects (bookmarks, links, etc.)
*
* NOTE: Set $wikka_id to NULL for all objects (except those marked
* private)
*/
function count_objects($wikka_id, $obj, $freetag_options) {
if(isset($wikka_id)) {
$wikka_sql = "AND wikka_id = '".mysql_real_escape_string($wikka_id)."'";
} else {
$wikka_sql = "AND private = 0";
}
$res = $obj->LoadSingle("select COUNT(*) as count from ".$freetag_options['db_name'].".freetag_bookmarks where 1 ".$wikka_sql.";");
$count = $res['count'];
return $count;
}
/********************************************************************/
/**
* count_tagged_objects()
*
* Returns a count of tagged objects (bookmarks, links, etc.)
*
* NOTE: Set $wikka_id to NULL for all taggedobjects regardless of
* owner (except those marked private)
*/
function count_tagged_objects($wikka_id, $tag, $obj, $freetag_options, $freetag) {
$num_objs = count_objects($wikka_id, $obj, $freetag_options);
$tagger_id = wikka_id_to_tagger_id($wikka_id, $obj, $freetag_options);
$ids = $freetag->get_most_recent_objects($tagger_id, $tag, 0, $num_objs);
return count($ids);
}
/********************************************************************/
/**
* save_to_current_session()
*
* Save various fields to the current session
*
* Input: Associative array of session tags->values
*
* Return: Name of session
*
*/
function save_to_current_session($values) {
foreach($values as $tag=>$value) {
$_SESSION[$tag] = $value;
}
return $_SESSION['name'];
}
/********************************************************************/
?>
%%
- Save the following file as ##libs/Wikka.freetag.class.php##:
**Wikka.freetag.class.php**
%%(php)
<?php
/*******************************************************************
* $Id: Wikka.freetag.class.php,v 1.8 2006/08/10 04:03:28 brian Exp brian $
*
* Wikka.freetag.class.php
*
* Extension to Gordon Luk's Freetag for PHP4 (see credits) for
* use with Wikka Bookmark action/handler
*
* Credits:
* Gordon Luk's Freetag for PHP4 (http://www.getluky.net/freetag)
*
* Author:
* Copyright (c) 2006 Brian Koontz <brian@pongonova.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public Licence for more details:
*
* http://www.gnu.org/copyleft/gpl.html
*
*********************************************************************/
// Check for freetag library
$freetag_lib = '3rdparty/plugins/freetag/freetag.class.php';
if(!is_file($freetag_lib)) {
print("<br/><br/><div class=\"error\">".$this->Format("Can't find $freetag_lib!")."<br/><br/>\n");
die();
}
include_once($freetag_lib);
/********************************************************************/
/* Freetag subclass (defines additional support methods) */
/********************************************************************/
class wikka_freetag extends freetag {
function wikka_freetag($freetag_options) {
parent::freetag($freetag_options);
$this->_normalized_valid_chars = '@_a-zA-Z0-9';
}

/*******************************************************************
* get_tag_cloud_html_with_limits
*
* Extension to get_tag_cloud_html() that permits specifying
* an offset and limit (useful for presenting more tags than
* can be displayed at one time).
*
* See get_tag_cloud_html() comments for description of this
* function.
*
* @param int Specify starting record (default: 0). Will be set to
* offset of first matching term if $search_term is not NULL
* @param int The maximum number of tags to return. (default: 100)
* @param int The minimum font size in the cloud. (default: 10)
* @param int The maximum number of tags to return. (default: 20)
* @param string The "units" for the font size (i.e. 'px', 'pt',
* 'em') (default: px)
* @param string The class to use for all spans in the cloud.
* (default: cloud_tag)
* @param string The tag page URL (default: /tag/)
* @param int The tagger ID (default: NULL)
* @param string Optional search term (default: NULL)
*
* @return string Returns an HTML snippet that can be used
* directly as a tag cloud.
*/
function get_tag_cloud_html_with_limits($offset = 0, $limit = 100, $min_font_size = 10, $max_font_size = 20, $font_units = 'px', $span_class = 'cloud_tag', $tag_page_url = '/tag/', $tagger_id = NULL, $search_term = NULL) {
$tag_list = $this->get_tag_cloud_tags_with_limits($offset, $limit, $tagger_id,$search_term);
// Get the maximum qty of tagged objects in the set
$max_qty = max(array_values($tag_list));
// Get the min qty of tagged objects in the set
$min_qty = min(array_values($tag_list));
// For ever additional tagged object from min to max, we add
// $step to the font size.
$spread = $max_qty - $min_qty;
if (0 == $spread) { // Divide by zero
$spread = 1;
}
$step = ($max_font_size - $min_font_size)/($spread);
// Since the original tag_list is alphabetically ordered,
// we can now create the tag cloud by just putting a span
// on each element, multiplying the diff between min and qty
// by $step.
$cloud_html = '';
$cloud_spans = array();
foreach ($tag_list as $tag => $qty) {
//if(strpos($tag, "private") !== false) continue;
$size = $min_font_size + ($qty - $min_qty) * $step;
$search_tag = "";
if(isset($search_term)) {
$search_tag = "&search_term=".$search_term;
}
$cloud_span[] = '<span class="' . $span_class . '" style="font-size: '. $size . $font_units . '"><a href="'.$tag_page_url . "&tag_offset=".$offset."&tag_limit=".$limit."&tag=".$tag.$search_tag. '">' . htmlspecialchars(stripslashes($tag)) . '</a></span>';
}
// Remove <br/> from the following line to get a "tag
// cloud" rather than a "tag list"
$cloud_html = join("<br/>\n ", $cloud_span);
return $cloud_html;
}
/*
* get_tag_cloud_tags_with_limits
*
* Extension to get_tag_cloud_tags() that permits specifying
* an offset and limit (useful for presenting more tags than
* can be displayed at one time). Also, tags are returned
* in true alphanumeric sequence across the entire record set,
* rather than across the most popular tags.
*
* See get_tag_cloud_tags() comments for description of this
* function.
* @param int Specify starting record (default: 0). Will be set to
* offset of first matching tag if $search_term is not NULL.
* @param int The maximum number of tags to return (default: 100)
* @param int Tag owner (default: NULL)
* @param string Optional search term (default: NULL)
*
* @return array Returns an array where the keys are normalized
* tags, and the
* values are numeric quantity of objects tagged with that tag.
*/
function get_tag_cloud_tags_with_limits($offset = 0, $limit = 100, $tagger_id = NULL, $search_term = NULL) {
$db = $this->db;
$limit_sql = "LIMIT ".$offset.", ".$limit;
if(isset($tagger_id) && ($tagger_id > 0)) {
$tagger_sql = "AND tagger_id = $tagger_id";
} else {
$tagger_sql = "";
}
$search_sql = "";
if(isset($search_term)) {
$search_sql = "AND tag LIKE \"".$search_term."%\"";
//$limit_sql = "LIMIT ".$limit;
//$offset = 0;
}
$prefix = $this->_table_prefix;
$sql = "SELECT tag, COUNT(object_id) AS quantity
FROM ${prefix}freetags INNER JOIN
${prefix}freetagged_objects
ON (${prefix}freetags.id = tag_id)
WHERE 1
$tagger_sql
$search_sql
GROUP BY tag
ORDER BY tag ASC
$limit_sql
";
$rs = $db->Execute($sql) or die("Syntax Error: $sql");
while(!$rs->EOF) {
$retarr[$rs->fields['tag']] = $rs->fields['quantity'];
$rs->MoveNext();
if($search_term) {
//$offset++;
}
}
ksort($retarr);
return $retarr;
}
/**
* count_unique_tags
* Returns the total number of unique tags in the system.
*
* @param int The unique ID of the person to restrict results to.
* @param string Optional search string (default: NULL)
*
* @return int Returns the count
*/
function count_unique_tags($tagger_id = NULL, $search_term = NULL) {
$db = $this->db;
if(isset($tagger_id) && ($tagger_id > 0)) {
$tagger_sql = "AND tagger_id = $tagger_id";
} else {
$tagger_sql = "";
}
$search_sql = "";
if(isset($search_term)) {
$search_sql = "AND tag LIKE \"".$search_term."%\"";
//$limit_sql = "LIMIT ".$limit;
//$offset = 0;
}
$prefix = $this->_table_prefix;
$sql = "SELECT DISTINCT tag, tag_id, COUNT(*) as count
FROM ${prefix}freetags INNER JOIN ${prefix}freetagged_objects ON (id = tag_id)
$tagger_sql
$search_sql
GROUP BY NULL
";
$rs = $db->Execute($sql) or die("Syntax Error: $sql");
if(!$rs->EOF) {
return $rs->fields['count'];
}
return false;
}
}
/********************************************************************/
?>
%%
- Adjust the DB connection parameters for your own installation (only necessary if you deviate from the DB instructions above)

- Create the CSS stylesheet for the action, ##css/bookmarkmgr.css##:
%%(php)
.bookmark_floatr {
float: right;
text-align: left;
width: 10%;
margin-left: 15px;
padding: 4px;
background: #EEE;
border: 1px solid #CCC;
line-height: 95%;
}
.bookmark_box {
width: 85%;
font-size: 85%;
padding: 0px 5px;
color: #666;
background: #EEE;
border: 1px solid #CCC;
}
.bookmark_block {
width: 85%;
border-bottom: 3px solid #DDD;
padding: 8px;
}
.bookmark_desc {
font-size: 100%;
font-weight: bold;
margin: 0px;
padding: 0px;
}
.bookmark_title {
font-size: 150%;
font-weight: bold;
padding: 5px 0px;
}
.pagination {
text-align: center;
margin: 20px auto;
}
%%
- In order to use the stylesheet, add the following lines to the files indicated (click [[http://wush.net/trac/wikka/ticket/246 here]] for a justification as to why these changes are necessary to the core):
**wikka.config.php:**
%%(php)
'additional_stylesheets' => 'bookmarkmgr.css',
%%
**actions/header.php:**
%%(php)
// Locate this line...
<link rel="stylesheet" type="text/css" href="css/<?php echo $this->GetConfigValue("stylesheet") ?>" />
// ...and add the following codeblock:
<?php
if($this->GetConfigValue("additional_stylesheets") != NULL) {
$sheets = preg_split("/[\s]+/", $this->GetConfigValue("additional_stylesheets"));
foreach($sheets as $sheet) {
echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"css/".$sheet."\" />\n";
}
}
?>
%%
- Add the ""{{bookmarks}}"" action code to a new page

==Bookmarklet support==
BookmarkManager now supports "bookmarklets," which is a small snippet of Javascript saved as a browser bookmark that allows you to bookmark a webpage "on the fly." The following patch must be applied to ##actions/usersettings.php## if you want to restrict bookmarklets to registered users (recommended):
%%(sh)
===================================================================
RCS file: RCS/usersettings.php,v
retrieving revision 1.1
diff -u -r1.1 usersettings.php
--- usersettings.php 2006/06/18 06:03:03 1.1
+++ usersettings.php 2006/06/24 06:40:13
@@ -1,5 +1,7 @@
<?php
/**
+ * $Id: usersettings.php,v 1.5 2006/06/24 06:40:07 brian Exp brian $
+ *
* Display a form to register, login and change user settings.
*
* @package Actions
@@ -334,6 +336,13 @@
// is user trying to log in or register?
if (isset($_POST['action']) && ($_POST['action'] == 'login'))
{
+ // Login request was redirected from elsewhere...let's make
+ // sure to go back if requested by setting $referrer
+ $referrer = null;
+ if(isset($_SESSION['go_back'])) {
+ $referrer = $_SESSION['go_back'];
+ }
+
// if user name already exists, check password
if (isset($_POST['name']) && $existingUser = $this->LoadUser($_POST['name']))
{
@@ -349,7 +358,11 @@
break;
default:
$this->SetUser($existingUser);
- $this->Redirect($this->href());
+ if($referrer == null)
+ $this->Redirect($this->href());
+ else {
+ $this->Redirect($referrer);
+ }
}
}
else // otherwise, proceed to registration
@@ -417,7 +430,11 @@

// log in
$this->SetUser($this->LoadUser($name));
- $this->Redirect($this->href('', '', 'registered=true'));
+ if($referrer == null)
+ $this->Redirect($this->href('', '', 'registered=true'));
+ else {
+ $this->Redirect($referrer);
+ }
}
}
}
@@ -542,4 +559,4 @@
<?php
print($this->FormClose());
}
%%
Save the following as a bookmark in your browser:
%%
javascript:location.href='http://your.site.com/wiki/wikka.php?wakka=BookmarkPage&action=add&uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&wikka_bookmarklet=1'
(Don't forget to change "your.site.com" as appropriate for your installation!)
%%
TODO: Implement as a popup:
%%
post bookmarklet (with popup): javascript:void(open('http://de.lirio.us/rubric/post?uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&when_done=close','Rubric','toolbar=no,width=700,height=325,scrollbars'));
%%
==Other stuff==
- [[BookmarkManagerImportScripts Import scripts]]

==Category==
CategoryDevelopmentDiscussion
CategoryDevelopmentActions
CategoryUserContributions
Deletions:
%% javascript:location.href='http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks


Revision [17443]

Edited on 2007-08-23 16:30:08 by IjzQ5y [reverted]
Additions:
%% javascript:location.href='http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks
Deletions:
%% javascript:location.href='http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks&action=add&uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&wikka_bookmarklet=1'
%%
then go to a webpage and click on the bookmark you just saved.
<<::c::
==Screenshots==
**Main page**
{{image class="left" alt="BookmarkManager main page" url="http://durango.dcccd.edu/images/ss1.jpg"}}
::c::
**Add bookmark page**
{{image class="left" alt="BookmarkManager add page" url="http://durango.dcccd.edu/images/ss2.jpg"}}
::c::
==Design ideas==
- Allow both user-specific and site-wide bookmark access (other than those tagged as "private")
- Allow tagging of objects other than URIs (such as pages)
- Implement as both a handler and an action
- Gordon Luk (creator of [[http://getluky.net/freetag/ Freetag]], the engine behind Bookmark Manager) has some interesting comments about tagging [[http://tagsonomy.com/index.php/interview-with-gordon-luk-freetag/ here]].
- Here's another [[http://www.integratedelf.com/wiki/doku.php?id=wikkawiki:about Wikka tagging project]] using the [[http://getluky.net/freetag Freetag]] engine. It would be useful to investigate the merging of that project with this one to permit page tagging in addition to bookmark tagging.
==Progress reports==
- **Beta test announcement:** Well, after hacking around with Scuttle, I decided it was much too complex for what I had in mind. Nils suggested another project, [[http://getluky.net/freetag/ Freetag]], that seems to provide the basic functionality needed without all of the formatting/presentation overhead. I have a beta up and running [[http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks here]], and would appreciate your comments and suggestions! Keep in mind this is still quite rough around the edges, but I was aiming more towards proof-of-concept. I think something like this would be a cool addition (as a 100% modular plugin) to Wikka...
- Progress is going well. Add/edit/delete capabilities have been added. Two Perl scripts (see below) have been provided that allow you to export your bookmarks from de.lirio.us and import them into BookmarkManager. Beta currently running under Wikka 1.1.6.2-alpha. A page of 25 links (out of 600+) and 100 tags (out of 1200+) renders in approximately 1.1 seconds. A tag seach of 1207 tags takes approximately 1.2 seconds.
- More enhancements and modifications: Numeric tags, link pagination, tag pagination, tag/link editing and deletion.
- More cool stuff: Support for bookmarklets (small Javascript bookmark that immediately saves the page you are viewing and returns to the page), tag links within link descriptors, better use of screen real estate using CSS.
- Tags are now searchable! Searchable titles/descriptions to come...
==TODO==
++Indicates issues that have been addressed++
- ++This code is most likely not yet safe for a production environment!++ //I've worked to secure all data passed in from GET/POST requests, as well as from Wikka itself in some cases. I'm hoping a few more folks have time to look over the code for any obvious security weaknesses.//
- User tags should be displayed as raw tags.
- As a general consideration it would be nice to provide this functionality as a handler.
- ++it would be nice to have a link for the add-form near list yours.++
- ++how about something like my tags / all tags?++ //List all/list yours now provides this functionality//
- what would be usefull: If I tag a link with the name of an existing wiki-page, this link could show up at the end of the page.
- ++Need to fix logic so "your" tags only display "your" links (also, identify tag cloud as "all" or "yours")++
- Optional logic to display tags in a right-justified vertical list instead of a cloud
- ++Paging++, user prefs
- Tag pages as well as links
- ++Please put the wikka_freetag class in a file like Wikka.freetag.class.php in the new \libs directory.++
- ++$freetag_options -> why not use the params provided by wikka ($this->mysql_host, etc.)?++
- ++Create bookmarklets such as these (borrowed from [[de.lirio.us]])++: //Implemented...see below for details//
- ++Provide edit/delete functionality++
- Search functionality (multi-tag, by title/desc)
- Restricting spam. Some ideas: ++Option to allow only registered users to set bookmarks++ //Implemented//; using "captcha" images to preclude automated scripts
- ++It would be nice if tags in each bookmarks were links pointing to the list of bookmarks with the same tag.++
- ++[[http://wikkawiki.org/BookmarkManager?show_comments=1&showall=1#comment_4505 Move markup to their own div tags]]++
- ++[[http://wikkawiki.org/BookmarkManager?show_comments=1&showall=1#comment_4507 Move helper functions, class extensions to libs/ dir]]++
- Fix PHP warnings w/r/t/ null arrays in ksort, min, max, etc.
- The codebase is getting large enough to consider distributing as a tarball/zipfile.
- Split processing of tags and links so they are independent of each other (AJAX??)
- Check/warn on link dupes during add new link operation
- Title and description: What's the diff? More useful to display URL in lieu of one of these?
- Export options: RSS, all to file, to screen (suitable for copy-and-paste), etc.
==Installation==
- Install a copy of [[http://code.google.com/p/freetag/ Freetag]] in **3rdparty/plugins/** (version 0.250 or better) //Note: Rename the installation directory (Freetag-0.250) to 'freetag'//
- Create a MySQL database (I used ##freetag## as the DB name) and use the MySQL dump below to create the necessary tables (such as ##mysql -u root freetag < wikka_freetag.sql##):
**wikka_freetag.sql**
%%(sql)
-- $Id: freetag.sql,v 1.2 2006/05/29 17:11:44 brian Exp brian $
--
-- MySQL dump 9.11
-- Freetag Structure v2.02
--
-- Table structure for table `freetags`
--
DROP TABLE IF EXISTS freetags;
CREATE TABLE freetags (
id int(10) unsigned NOT NULL auto_increment,
tag varchar(30) NOT NULL default '',
raw_tag varchar(50) NOT NULL default '',
PRIMARY KEY (id)
) TYPE=MyISAM;
--
-- Table structure for table `freetagged_objects`
--
DROP TABLE IF EXISTS freetagged_objects;
CREATE TABLE freetagged_objects (
tag_id int(10) unsigned NOT NULL default '0',
tagger_id int(10) unsigned NOT NULL default '0',
object_id int(10) unsigned NOT NULL default '0',
tagged_on datetime NOT NULL default '0000-00-00 00:00:00',
PRIMARY KEY (`tag_id`,`tagger_id`,`object_id`),
KEY `tag_id_index` (`tag_id`),
KEY `tagger_id_index` (`tagger_id`),
KEY `object_id_index` (`object_id`)
) TYPE=MyISAM;
--
-- Table structure for table `freetag_bookmarks`
--
DROP TABLE IF EXISTS freetag_bookmarks;
CREATE TABLE freetag_bookmarks (
bookmark_id int(10) unsigned NOT NULL auto_increment,
title varchar(255) NOT NULL default '',
uri varchar(255) NOT NULL,
description varchar(255) default NULL,
wikka_id varchar(75) NOT NULL,
private tinyint(1) NOT NULL default '0',
PRIMARY KEY (`bookmark_id`),
KEY `bookmark_id_index` (`bookmark_id`)
) TYPE=MyISAM;
--
-- Table structure for table `freetag_wikka_id_map`
--
DROP TABLE IF EXISTS freetag_wikka_id_map;
CREATE TABLE freetag_wikka_id_map (
tagger_id int(10) unsigned NOT NULL AUTO_INCREMENT,
wikka_id varchar(75) NOT NULL default '',
PRIMARY KEY(`tagger_id`)
) TYPE=MyISAM;
%%
- Save the following file as ##actions/bookmarks.php##:
**bookmarks.php**
%%(php)
<?php
/**
* Bookmark manager with tagging
*
* Allows users (registered or not) to tag objects such as
* bookmarks and display linked tags in the spirit of
* {@link http://de.licio.us} and {@link http://de.lirio.us}.
*
* Usage: {{bookmarks}}
*
* Credits:
* Gordon Luk's Freetag for PHP4 {@link http://www.getluky.net/freetag}
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public Licence for more details:
*
* http://www.gnu.org/copyleft/gpl.html
*
*
* @author {@link http://wikkawiki.org/BrianKoontz Brian Koontz} <brian@pongonova.net>
* @copyright Copyright (c) 2006, Brian Koontz <brian@pongonova.net>
* @name BookmarkManager
* @package Actions
* @filesource
* @license http://www.gnu.org/copyleft/gpl.html
* @since Wikka 1.1.6.1
* @version $Id: bookmarks.php,v 1.8 2006/08/10 04:03:38 brian Exp brian $
*
*/
// Check for freetag class and helper libraries
include_once('libs/Wikka.freetag.class.php');
include_once('libs/Wikka.freetag.lib.php');
/********************************************************************/
/* DB connection params */
/********************************************************************/
$freetag_options = array(
'db_user' => $this->config['mysql_user'],
'db_pass' => $this->config['mysql_password'],
'db_host' => $this->config['mysql_host'],
'db_name' => 'freetag',
'PCONNECT' => true,
);
$freetag = new wikka_freetag($freetag_options);
$wikka_id = $this->GetUserName();
$tagger_id = wikka_id_to_tagger_id($wikka_id, $this, $freetag_options);
$all_obj_count = count_objects(NULL, $this, $freetag_options);
$your_obj_count = count_objects($wikka_id, $this, $freetag_options);
$search_type = "";
if(isset($_REQUEST['action'])) {
if($_REQUEST['action']=="list_yours") {
$search_type = "search_yours";
} else {
$search_type = "search_all";
}
}
echo "<a href=\"".$this->href()."&action=list_all"."\">List all</a> (".$all_obj_count.") | ".
"<a href=\"".$this->href()."&action=list_yours"."\">List yours</a> (".$your_obj_count.") | ".
"<a href=\"".$this->href()."&action=add"."\">Add</a>".
$this->FormOpen().
"<input type=\"hidden\" name=\"action\" value=\"".$search_type."\" />".
"Search: ".
"<input name=\"search\" size=\"20\" />".
"<input type=\"radio\" name=\"search_type\" value=\"tags\" checked>Tags".
"<input type=\"radio\" name=\"search_type\" value=\"title_desc\">Title/Desc".
$this->FormClose();
if(!isset($_REQUEST['action'])) {
// Display all tags at start of session
$this->Redirect($this->href()."&action=list_all");
}
// Search by tags, titles, or descriptions
// Action name: search
// Search field name: search
// Radio buttons: search_type: "tags" or "title_desc"
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="search_yours") || ($_REQUEST['action']=="search_all")) {
if(isset($_REQUEST['search_type']) &&
($_REQUEST['search_type']=="tags") &&
isset($_REQUEST['search'])) {
if($_REQUEST['action'] == "search_yours") {
$this->Redirect($this->href()."&search_term=".$_REQUEST['search']."&action=list_yours");
} else {
$this->Redirect($this->href()."&search_term=".$_REQUEST['search']."&action=list_all");
}
}
}
// Add new entry to DB
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="add")) {
$values = array();
// Check for request from a bookmarklet...check to see if user
// is logged in...if not, redirect to login page and come back
// here when done
if(isset($_REQUEST['wikka_bookmarklet'])) {
$values['uri'] = mysql_real_escape_string($_REQUEST['uri']);
if(isset($_REQUEST['title'])) {
$values['title'] = mysql_real_escape_string($_REQUEST['title']);
}
$values['referrer'] = $_SERVER['HTTP_REFERER'];
$values['when_done'] = "add_link";
save_to_current_session($values);
//session_write_close();
if(!$this->GetUser()) {
$_SESSION['go_back'] = $this->Href(null, $this->MiniHref()."&action=add");
//session_write_close();
$this->Redirect($this->config['base_url']."UserSettings");
}
}
// The next clause checks to see if we're returning from a
// login request while trying to execute a bookmarklet
if(isset($_SESSION['when_done']) && ($_SESSION['when_done']=="add_link")) {
$values['uri'] = $_SESSION['uri'];
$values['title'] = $_SESSION['title'];
$_SESSION['when_done'] = "return";
//session_write_close();
}
if(trim($_REQUEST['tags']) != "") {
// Private bookmark?
$private = 0;
if(strpos(trim($_REQUEST['tags']), "@private") !== false)
$private = 1;

// Converts numeric tag "123" to "123_" to facilitate
// alphanumeric sorting (otherwise, PHP converts string to
// true integer).
$tags = preg_split('/\s+/', $_REQUEST['tags'], -1, PREG_SPLIT_NO_EMPTY);
$tags = preg_replace('/^([0-9]+)$/', "$1_", $tags);
$tags = implode(" ", $tags);
$this->Query("insert ".$freetag_options['db_name'].".freetag_bookmarks set ".
"title = '".mysql_real_escape_string($_REQUEST['title'])."', ".
"uri = '".mysql_real_escape_string($this->cleanUrl($_REQUEST['uri']))."', ".
"wikka_id = '".mysql_real_escape_string($wikka_id)."', ".
"private = '".$private."', ".
"description = '".mysql_real_escape_string($_REQUEST['desc'])."';");
$last_id = $this->LoadSingle("select last_insert_id();");
$freetag->tag_object($tagger_id, $last_id['last_insert_id()'], $tags);
// Check to see if it's time to return to the page from which
// the bookmarklet was called
if(isset($_SESSION['when_done']) && ($_SESSION['when_done']=="return")) {
if(isset($_SESSION['referrer'])) {
$referrer = $_SESSION['referrer'];
unset($_SESSION['go_back']);
unset($_SESSION['uri']);
unset($_SESSION['title']);
unset($_SESSION['referrer']);
unset($_SESSION['when_done']);
$this->Redirect($referrer);
}
}
// Otherwise, go back to tag/link list view
$this->Redirect($this->href()."&action=list_yours");
} else {
// Display add form
print($this->FormOpen());
?>
<input type="hidden" name="action" value="add" />
<table>
<tr>
<td align="right">Title:</td>
<td><input name="title" size="40" value="<?php echo $values['title']; ?>" /></td>
</tr>
<tr>
<td align="right">URI:</td> <td><input name="uri" size="40" value="<?php echo $values['uri']; ?>" /></td> </tr>
<tr>
<td align="right">Description:</td>
<td><input name="desc" size="40"/></td>
</tr>
<tr>
<td align="right"></td>
<td><?php echo $this->Format("Use blank space between tags, include \"@private\" for private bookmark"); ?></td>
</tr>
<tr>
<td align="right">Tags:</td>
<td><input name="tags" size="40"/></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Add" size="40" /></td>
</tr>
</table>
<?php
print($this->FormClose());
}
}
// Edit entry
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="edit") && (isset($_REQUEST['object_id']))) {
$object_id = mysql_real_escape_string($_REQUEST['object_id']);
$obj = $this->LoadSingle("select bookmark_id,title,uri,description,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$object_id.";");
// Is the logged-in user the owner?
if($wikka_id != $obj['wikka_id']) {
$this->Redirect($this->href()."&action=list_all");
}
if(isset($_REQUEST['modify']) && trim($_REQUEST['tags']) != "") {
// Delete all tags, then re-tag
$freetag->delete_all_object_tags_for_user($tagger_id,$object_id);
// Private bookmark?
$private = 0;
if(strpos(trim($_REQUEST['tags']), "@private") !== false)
$private = 1;
$this->Query("update ".$freetag_options['db_name'].".freetag_bookmarks set ".
"title = '".mysql_real_escape_string($_REQUEST['title'])."', ".
"uri = '".mysql_real_escape_string($this->cleanUrl($_REQUEST['uri']))."', ".
"wikka_id = '".mysql_real_escape_string($wikka_id)."', ".
"private = '".$private."', ".
"description = '".mysql_real_escape_string($_REQUEST['desc'])."' ".
"where bookmark_id = ".mysql_real_escape_string($object_id)." ".
"limit 1;");
$freetag->tag_object($tagger_id, $obj['bookmark_id'], $_REQUEST['tags']);
$this->Redirect($this->href()."&action=list_yours");
}
// Display add form
print($this->FormOpen());
?>
<input type="hidden" name="action" value="edit" />
<input type="hidden" name="modify" value="yes" />
<input type="hidden" name="object_id" value="<?php print $object_id ?>" />
<table>
<tr>
<td align="right">Title:</td>
<td><input name="title" size="40" value="<?php print $obj['title']; ?>"/></td>
</tr>
<tr>
<td align="right">URI:</td>
<td><input name="uri" size="40" value="<?php print $obj['uri']; ?>"/></td>
</tr>
<tr>
<td align="right">Description:</td>
<td><input name="desc" size="40" value="<?php print $obj['description']; ?>"/></td>
</tr>
<tr>
<td align="right"></td>
<td><?php echo $this->Format("Use blank space between tags, include \"@private\" for private bookmark"); ?></td>
</tr>
<tr>
<td align="right">Tags:</td>
<?php
$tags = $freetag->get_tags_on_object($object_id);
$taglist = "";
foreach($tags as $idx=>$res) {
$taglist .= trim($res['raw_tag'])." ";
}
?>
<td><input name="tags" size="40" value="<?php print $taglist?>"/></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Edit" size="40" /></td>
</tr>
</table>
<?php
print($this->FormClose());
}
// Delete entry
if(isset($_REQUEST['action']) && $_REQUEST['action']=="delete") {
$object_id = mysql_real_escape_string($_REQUEST['object_id']);
$obj = $this->LoadSingle("select bookmark_id,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".mysql_real_escape_string($object_id).";");
// Is the logged-in user the owner?
if($wikka_id != $obj['wikka_id']) {
$this->Redirect($this->href()."&action=list_all");
}
// Delete all tags first...
$freetag->delete_all_object_tags_for_user($tagger_id,$object_id);
// ...then delete object
$this->Query("delete from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".mysql_real_escape_string($object_id)." limit 1;");
$this->Redirect($this->href()."&action=list_yours");
}
// List all bookmarks (ordered by most recent)
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="list_all")) {
$offset = 0;
if(isset($_REQUEST['offset'])) {
$offset = mysql_real_escape_string($_REQUEST['offset']);
}
$limit = 25;
if(isset($_REQUEST['limit'])) {
$limit = mysql_real_escape_string($_REQUEST['limit']);
}
$tag_offset = 0;
if(isset($_REQUEST['tag_offset'])) {
$tag_offset = mysql_real_escape_string($_REQUEST['tag_offset']);
}
$tag_limit = 100;
if(isset($_REQUEST['tag_limit'])) {
$tag_limit = mysql_real_escape_string($_REQUEST['tag_limit']);
}
// Output tag cloud
$url_opts = $this->href()."&action=list_all";
$search_term = NULL;
$header = "All tags";
if(isset($_REQUEST['search_term'])) {
$search_term = mysql_real_escape_string($_REQUEST['search_term']);
$header = "All tags by search";
}
output_tag_cloud($freetag,$url_opts,NULL,$header,$tag_offset,$tag_limit,$search_term);
$ids = "";
$tag = "";
if(isset($_REQUEST['tag']) && $_REQUEST['tag'] != "") {
$ids = $freetag->get_most_recent_objects(NULL,$_REQUEST['tag'],$offset,$limit);
$tag = mysql_real_escape_string($_REQUEST['tag']);
} else {
$ids = $freetag->get_most_recent_objects(NULL,NULL,$offset,$limit);
}
foreach($ids as $key=>$val) {
$tags = $freetag->get_tags_on_object($val['object_id']);
$obj = $this->LoadSingle("select title,uri,description,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$val['object_id']." and private = 0;");
if(!isset($obj)) {
continue;
}
print "<div class=\"bookmark_block\">\n";
print "<div class=\"bookmark_title\"><a href=\"".$obj['uri']."\">".$obj['title']."</a></div>\n";
print "<div class=\"bookmark_box\"><div class=\"bookmark_desc\">".($obj['description'] ? $obj['description'] : " ")."</div>\n";
$taglist = "";
$link = $url_opts."&tag_offset=".$tag_offset."&tag_limit=".$tag_limit;
foreach($tags as $idx=>$res) {
$temp_tag = trim($res['tag']);
$taglist .= "<a href=\"".$link."&tag=".$temp_tag."\">".$temp_tag."</a> ";
}
print $taglist."by ".$obj['wikka_id']." (created: ".$val['tagged_on'].")\n";
//print "</div><div class=\"clear\"> </div><hr/>";
print "</div></div>\n";
}
// Display pagination links
$obj_count = 0;
$tag_url = "";
if($tag != "") {
$obj_count = count_tagged_objects(NULL, $tag, $this, $freetag_options, $freetag);
$tag_url = "&tag=".$tag;
} else {
$obj_count = count_objects(NULL, $this, $freetag_options);
}
$prev_offset = $offset - $limit;
$prev_url = "";
if($prev_offset < 0) {
$prev_offset = 0;
}
if($offset > 0) {
$prev_url = "<a href=\"".$this->href()."&action=list_all&offset=".$prev_offset."&limit=".$limit.$tag_url."\"><<</a>";
}
$next_offset = $offset + $limit;
$next_url = "";
if($next_offset < $obj_count - 1) {
$next_url = "<a href=\"".$this->href()."&action=list_all&offset=".$next_offset."&limit=".$limit.$tag_url."\">>></a>";
}
print "<div class=\"pagination\">";
print $prev_url."  ".$next_url;
print "</div>";
}
// List user bookmarks (ordered by most recent)
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="list_yours")) {
$offset = 0;
if(isset($_REQUEST['offset'])) {
$offset = mysql_real_escape_string($_REQUEST['offset']);
}
$limit = 25;
if(isset($_REQUEST['limit'])) {
$limit = mysql_real_escape_string($_REQUEST['limit']);
}
$tag_offset = 0;
if(isset($_REQUEST['tag_offset'])) {
$tag_offset = mysql_real_escape_string($_REQUEST['tag_offset']);
}
$tag_limit = 100;
if(isset($_REQUEST['tag_limit'])) {
$tag_limit = mysql_real_escape_string($_REQUEST['tag_limit']);
}
// Output tag cloud
$url_opts = $this->href()."&action=list_yours";
$search_term = NULL;
$header = "Your tags";
if(isset($_REQUEST['search_term'])) {
$search_term = mysql_real_escape_string($_REQUEST['search_term']);
$header = "Your tags by search";
}
output_tag_cloud($freetag,$url_opts,$tagger_id,$header,$tag_offset,$tag_limit,$search_term);
$ids = "";
$tag = "";
if(isset($_REQUEST['tag']) && $_REQUEST['tag'] != "") {
$ids = $freetag->get_most_recent_objects($tagger_id, $_REQUEST['tag'], $offset, $limit);
$tag = mysql_real_escape_string($_REQUEST['tag']);
} else {
$ids = $freetag->get_most_recent_objects($tagger_id, NULL, $offset, $limit);
}
foreach($ids as $key=>$val) {
$tags = $freetag->get_tags_on_object($val['object_id']);
$obj = $this->LoadSingle("select title,uri,description,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$val['object_id'].";");
print "<div class=\"bookmark_block\">\n";
print "<div class=\"bookmark_title\"><a href=\"".$obj['uri']."\">".$obj['title']."</a></div>\n";
print "<div class=\"bookmark_box\"><div class=\"bookmark_desc\">".($obj['description'] ? $obj['description'] : " ")."</div>\n";
print " <a href=\"".$this->href()."&action=edit&object_id=".$val['object_id']."\">edit</a>";
print "|";
print "<a href=\"".$this->href()."&action=delete&object_id=".$val['object_id']."\">delete</a><br/>";
$taglist = "";
$link = $url_opts."&tag_offset=".$tag_offset."&tag_limit=".$tag_limit;
foreach($tags as $idx=>$res) {
$normtag = trim($res['tag']);
$rawtag = trim($res['raw_tag']);
$taglist .= "<a href=\"".$link."&tag=".$normtag."\">".$rawtag."</a> ";
}
print $taglist."by ".$obj['wikka_id']." (created: ".$val['tagged_on'].")\n";
//print "<div class=\"clear\"> </div><hr/>";
print "</div></div>\n";
}
// Display pagination links
$obj_count = 0;
$tag_url = "";
if($tag != "") {
$obj_count = count_tagged_objects($wikka_id, $tag, $this, $freetag_options, $freetag);
$tag_url = "&tag=".$tag;
} else {
$obj_count = count_objects($wikka_id, $this, $freetag_options);
}
$prev_offset = $offset - $limit;
$prev_url = "";
if($prev_offset < 0) {
$prev_offset = 0;
}
if($offset > 0) {
$prev_url = "<a href=\"".$this->href()."&action=list_yours&offset=".$prev_offset."&limit=".$limit.$tag_url."\"><<</a>";
}
$next_offset = $offset + $limit;
$next_url = "";
if($next_offset < $obj_count - 1) {
$next_url = "<a href=\"".$this->href()."&action=list_yours&offset=".$next_offset."&limit=".$limit.$tag_url."\">>></a>";
}
print "<div class=\"center\">";
print $prev_url."  ".$next_url;
print "</div>";
}
?>
%%
- Save the following file as ##libs/Wikka.freetag.lib.php##:
**Wikka.freetag.lib.php**
%%(php)
<?php
/**
* Wikka.freetag.lib.php
*
* Set of helper classes for BookmarkManager. The reason these
* aren't included in the Freetag extension class
* (Wikka.freetag.class.php) is that Wikka functions defined
* outside the scope of the class aren't accessible from within
* the class, and most of these helper functions use both Freetag
* and Wikka functions.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public Licence for more details:
*
* http://www.gnu.org/copyleft/gpl.html
*
*
* @author {@link http://wikkawiki.org/BrianKoontz Brian Koontz} <brian@pongonova.net>
* @copyright Copyright (c) 2006, Brian Koontz <brian@pongonova.net>
* @name Wikka.freetag.lib.php
* @package Actions {@link http://www.wikkawiki.org/BookmarkManager}
* @filesource
* @license http://www.gnu.org/copyleft/gpl.html
* @since Wikka 1.1.6.1
* @version $Id: Wikka.freetag.lib.php,v 1.3 2006/08/10 04:03:17 brian Exp brian $
*
*/
/*********************************************************************/
/* Helper functions */
/*********************************************************************/
/**
* wikka_id_to_tagger_id()
*
* Converts Wikka id (returned from GetUserName()) to
* tagger_id. Adds Wikka id to table if not already present.
* Maps NULL -> NULL.
*
*/
function wikka_id_to_tagger_id ($wikka_id, $obj, $freetag_options) {
if(!isset($wikka_id)) {
return NULL;
}
$wikka_id = mysql_real_escape_string($wikka_id);
$res = $obj->LoadSingle("select tagger_id from ".$freetag_options['db_name'].".freetag_wikka_id_map where wikka_id = '".$wikka_id."';");
$tagger_id = $res['tagger_id'];
if(!$tagger_id) {
$obj->Query("insert ".$freetag_options['db_name'].".freetag_wikka_id_map set "."wikka_id = '".$wikka_id."';");
$res = $obj->LoadSingle("select last_insert_id();");
$tagger_id = $res['last_insert_id()'];
}
return $tagger_id;
}
/********************************************************************/
/**
* output_tag_cloud()
*
* Create a cloud of tags!
*
* NOTES:
* Set $tagger_id to NULL for all tags (except those marked
* private)
*
* If $search_term is not NULL, $tag_offset will return the offset
* of the first matching term
*/
function output_tag_cloud($freetag,$tag_page_url,$tagger_id=NULL,$header=NULL,$tag_offset=0,$tag_limit=100,$search_term=NULL) {
// Output tag cloud
$tag_count = $freetag->count_unique_tags($tagger_id,$search_term);
print "<div class='bookmark_floatr'>";
if($header) {
print $header." (".$tag_count."):<br/>";
}
print $freetag->get_tag_cloud_html_with_limits($tag_offset,$tag_limit,10,20,'px','cloud_tag',$tag_page_url,$tagger_id,$search_term);
// Display pagination links
$tag_url = "";
$prev_offset = $tag_offset - $tag_limit;
$prev_url = "";
if($prev_offset < 0) {
$prev_offset = 0;
}
$search_tag = "";
if(isset($search_term)) {
$search_tag = "&search_term=".$search_term;
}
if($tag_offset > 0) {
$prev_url = "<a href=\"".$tag_page_url."&tag_offset=".$prev_offset."&tag_limit=".$tag_limit.$search_tag."\"><<</a>";
}
$next_offset = $tag_offset + $tag_limit;
$next_url = "";
if($next_offset < $tag_count - 1) {
$next_url = "<a href=\"".$tag_page_url."&tag_offset=".$next_offset."&tag_limit=".$tag_limit.$search_tag.$tag_url."\">>></a>";
}
print "<div class=\"pagination\">";
print $prev_url."  ".$next_url;
print "</div>";
print "</div>";
// print "<div class=\"clear\"> </div>";
}
/********************************************************************/
/**
* count_objects()
*
* Returns a count of objects (bookmarks, links, etc.)
*
* NOTE: Set $wikka_id to NULL for all objects (except those marked
* private)
*/
function count_objects($wikka_id, $obj, $freetag_options) {
if(isset($wikka_id)) {
$wikka_sql = "AND wikka_id = '".mysql_real_escape_string($wikka_id)."'";
} else {
$wikka_sql = "AND private = 0";
}
$res = $obj->LoadSingle("select COUNT(*) as count from ".$freetag_options['db_name'].".freetag_bookmarks where 1 ".$wikka_sql.";");
$count = $res['count'];
return $count;
}
/********************************************************************/
/**
* count_tagged_objects()
*
* Returns a count of tagged objects (bookmarks, links, etc.)
*
* NOTE: Set $wikka_id to NULL for all taggedobjects regardless of
* owner (except those marked private)
*/
function count_tagged_objects($wikka_id, $tag, $obj, $freetag_options, $freetag) {
$num_objs = count_objects($wikka_id, $obj, $freetag_options);
$tagger_id = wikka_id_to_tagger_id($wikka_id, $obj, $freetag_options);
$ids = $freetag->get_most_recent_objects($tagger_id, $tag, 0, $num_objs);
return count($ids);
}
/********************************************************************/
/**
* save_to_current_session()
*
* Save various fields to the current session
*
* Input: Associative array of session tags->values
*
* Return: Name of session
*
*/
function save_to_current_session($values) {
foreach($values as $tag=>$value) {
$_SESSION[$tag] = $value;
}
return $_SESSION['name'];
}
/********************************************************************/
?>
%%
- Save the following file as ##libs/Wikka.freetag.class.php##:
**Wikka.freetag.class.php**
%%(php)
<?php
/*******************************************************************
* $Id: Wikka.freetag.class.php,v 1.8 2006/08/10 04:03:28 brian Exp brian $
*
* Wikka.freetag.class.php
*
* Extension to Gordon Luk's Freetag for PHP4 (see credits) for
* use with Wikka Bookmark action/handler
*
* Credits:
* Gordon Luk's Freetag for PHP4 (http://www.getluky.net/freetag)
*
* Author:
* Copyright (c) 2006 Brian Koontz <brian@pongonova.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public Licence for more details:
*
* http://www.gnu.org/copyleft/gpl.html
*
*********************************************************************/
// Check for freetag library
$freetag_lib = '3rdparty/plugins/freetag/freetag.class.php';
if(!is_file($freetag_lib)) {
print("<br/><br/><div class=\"error\">".$this->Format("Can't find $freetag_lib!")."<br/><br/>\n");
die();
}
include_once($freetag_lib);
/********************************************************************/
/* Freetag subclass (defines additional support methods) */
/********************************************************************/
class wikka_freetag extends freetag {
function wikka_freetag($freetag_options) {
parent::freetag($freetag_options);
$this->_normalized_valid_chars = '@_a-zA-Z0-9';
}

/*******************************************************************
* get_tag_cloud_html_with_limits
*
* Extension to get_tag_cloud_html() that permits specifying
* an offset and limit (useful for presenting more tags than
* can be displayed at one time).
*
* See get_tag_cloud_html() comments for description of this
* function.
*
* @param int Specify starting record (default: 0). Will be set to
* offset of first matching term if $search_term is not NULL
* @param int The maximum number of tags to return. (default: 100)
* @param int The minimum font size in the cloud. (default: 10)
* @param int The maximum number of tags to return. (default: 20)
* @param string The "units" for the font size (i.e. 'px', 'pt',
* 'em') (default: px)
* @param string The class to use for all spans in the cloud.
* (default: cloud_tag)
* @param string The tag page URL (default: /tag/)
* @param int The tagger ID (default: NULL)
* @param string Optional search term (default: NULL)
*
* @return string Returns an HTML snippet that can be used
* directly as a tag cloud.
*/
function get_tag_cloud_html_with_limits($offset = 0, $limit = 100, $min_font_size = 10, $max_font_size = 20, $font_units = 'px', $span_class = 'cloud_tag', $tag_page_url = '/tag/', $tagger_id = NULL, $search_term = NULL) {
$tag_list = $this->get_tag_cloud_tags_with_limits($offset, $limit, $tagger_id,$search_term);
// Get the maximum qty of tagged objects in the set
$max_qty = max(array_values($tag_list));
// Get the min qty of tagged objects in the set
$min_qty = min(array_values($tag_list));
// For ever additional tagged object from min to max, we add
// $step to the font size.
$spread = $max_qty - $min_qty;
if (0 == $spread) { // Divide by zero
$spread = 1;
}
$step = ($max_font_size - $min_font_size)/($spread);
// Since the original tag_list is alphabetically ordered,
// we can now create the tag cloud by just putting a span
// on each element, multiplying the diff between min and qty
// by $step.
$cloud_html = '';
$cloud_spans = array();
foreach ($tag_list as $tag => $qty) {
//if(strpos($tag, "private") !== false) continue;
$size = $min_font_size + ($qty - $min_qty) * $step;
$search_tag = "";
if(isset($search_term)) {
$search_tag = "&search_term=".$search_term;
}
$cloud_span[] = '<span class="' . $span_class . '" style="font-size: '. $size . $font_units . '"><a href="'.$tag_page_url . "&tag_offset=".$offset."&tag_limit=".$limit."&tag=".$tag.$search_tag. '">' . htmlspecialchars(stripslashes($tag)) . '</a></span>';
}
// Remove <br/> from the following line to get a "tag
// cloud" rather than a "tag list"
$cloud_html = join("<br/>\n ", $cloud_span);
return $cloud_html;
}
/*
* get_tag_cloud_tags_with_limits
*
* Extension to get_tag_cloud_tags() that permits specifying
* an offset and limit (useful for presenting more tags than
* can be displayed at one time). Also, tags are returned
* in true alphanumeric sequence across the entire record set,
* rather than across the most popular tags.
*
* See get_tag_cloud_tags() comments for description of this
* function.
* @param int Specify starting record (default: 0). Will be set to
* offset of first matching tag if $search_term is not NULL.
* @param int The maximum number of tags to return (default: 100)
* @param int Tag owner (default: NULL)
* @param string Optional search term (default: NULL)
*
* @return array Returns an array where the keys are normalized
* tags, and the
* values are numeric quantity of objects tagged with that tag.
*/
function get_tag_cloud_tags_with_limits($offset = 0, $limit = 100, $tagger_id = NULL, $search_term = NULL) {
$db = $this->db;
$limit_sql = "LIMIT ".$offset.", ".$limit;
if(isset($tagger_id) && ($tagger_id > 0)) {
$tagger_sql = "AND tagger_id = $tagger_id";
} else {
$tagger_sql = "";
}
$search_sql = "";
if(isset($search_term)) {
$search_sql = "AND tag LIKE \"".$search_term."%\"";
//$limit_sql = "LIMIT ".$limit;
//$offset = 0;
}
$prefix = $this->_table_prefix;
$sql = "SELECT tag, COUNT(object_id) AS quantity
FROM ${prefix}freetags INNER JOIN
${prefix}freetagged_objects
ON (${prefix}freetags.id = tag_id)
WHERE 1
$tagger_sql
$search_sql
GROUP BY tag
ORDER BY tag ASC
$limit_sql
";
$rs = $db->Execute($sql) or die("Syntax Error: $sql");
while(!$rs->EOF) {
$retarr[$rs->fields['tag']] = $rs->fields['quantity'];
$rs->MoveNext();
if($search_term) {
//$offset++;
}
}
ksort($retarr);
return $retarr;
}
/**
* count_unique_tags
* Returns the total number of unique tags in the system.
*
* @param int The unique ID of the person to restrict results to.
* @param string Optional search string (default: NULL)
*
* @return int Returns the count
*/
function count_unique_tags($tagger_id = NULL, $search_term = NULL) {
$db = $this->db;
if(isset($tagger_id) && ($tagger_id > 0)) {
$tagger_sql = "AND tagger_id = $tagger_id";
} else {
$tagger_sql = "";
}
$search_sql = "";
if(isset($search_term)) {
$search_sql = "AND tag LIKE \"".$search_term."%\"";
//$limit_sql = "LIMIT ".$limit;
//$offset = 0;
}
$prefix = $this->_table_prefix;
$sql = "SELECT DISTINCT tag, tag_id, COUNT(*) as count
FROM ${prefix}freetags INNER JOIN ${prefix}freetagged_objects ON (id = tag_id)
$tagger_sql
$search_sql
GROUP BY NULL
";
$rs = $db->Execute($sql) or die("Syntax Error: $sql");
if(!$rs->EOF) {
return $rs->fields['count'];
}
return false;
}
}
/********************************************************************/
?>
%%
- Adjust the DB connection parameters for your own installation (only necessary if you deviate from the DB instructions above)

- Create the CSS stylesheet for the action, ##css/bookmarkmgr.css##:
%%(php)
.bookmark_floatr {
float: right;
text-align: left;
width: 10%;
margin-left: 15px;
padding: 4px;
background: #EEE;
border: 1px solid #CCC;
line-height: 95%;
}
.bookmark_box {
width: 85%;
font-size: 85%;
padding: 0px 5px;
color: #666;
background: #EEE;
border: 1px solid #CCC;
}
.bookmark_block {
width: 85%;
border-bottom: 3px solid #DDD;
padding: 8px;
}
.bookmark_desc {
font-size: 100%;
font-weight: bold;
margin: 0px;
padding: 0px;
}
.bookmark_title {
font-size: 150%;
font-weight: bold;
padding: 5px 0px;
}
.pagination {
text-align: center;
margin: 20px auto;
}
%%
- In order to use the stylesheet, add the following lines to the files indicated (click [[http://wush.net/trac/wikka/ticket/246 here]] for a justification as to why these changes are necessary to the core):
**wikka.config.php:**
%%(php)
'additional_stylesheets' => 'bookmarkmgr.css',
%%
**actions/header.php:**
%%(php)
// Locate this line...
<link rel="stylesheet" type="text/css" href="css/<?php echo $this->GetConfigValue("stylesheet") ?>" />
// ...and add the following codeblock:
<?php
if($this->GetConfigValue("additional_stylesheets") != NULL) {
$sheets = preg_split("/[\s]+/", $this->GetConfigValue("additional_stylesheets"));
foreach($sheets as $sheet) {
echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"css/".$sheet."\" />\n";
}
}
?>
%%
- Add the ""{{bookmarks}}"" action code to a new page

==Bookmarklet support==
BookmarkManager now supports "bookmarklets," which is a small snippet of Javascript saved as a browser bookmark that allows you to bookmark a webpage "on the fly." The following patch must be applied to ##actions/usersettings.php## if you want to restrict bookmarklets to registered users (recommended):
%%(sh)
===================================================================
RCS file: RCS/usersettings.php,v
retrieving revision 1.1
diff -u -r1.1 usersettings.php
--- usersettings.php 2006/06/18 06:03:03 1.1
+++ usersettings.php 2006/06/24 06:40:13
@@ -1,5 +1,7 @@
<?php
/**
+ * $Id: usersettings.php,v 1.5 2006/06/24 06:40:07 brian Exp brian $
+ *
* Display a form to register, login and change user settings.
*
* @package Actions
@@ -334,6 +336,13 @@
// is user trying to log in or register?
if (isset($_POST['action']) && ($_POST['action'] == 'login'))
{
+ // Login request was redirected from elsewhere...let's make
+ // sure to go back if requested by setting $referrer
+ $referrer = null;
+ if(isset($_SESSION['go_back'])) {
+ $referrer = $_SESSION['go_back'];
+ }
+
// if user name already exists, check password
if (isset($_POST['name']) && $existingUser = $this->LoadUser($_POST['name']))
{
@@ -349,7 +358,11 @@
break;
default:
$this->SetUser($existingUser);
- $this->Redirect($this->href());
+ if($referrer == null)
+ $this->Redirect($this->href());
+ else {
+ $this->Redirect($referrer);
+ }
}
}
else // otherwise, proceed to registration
@@ -417,7 +430,11 @@

// log in
$this->SetUser($this->LoadUser($name));
- $this->Redirect($this->href('', '', 'registered=true'));
+ if($referrer == null)
+ $this->Redirect($this->href('', '', 'registered=true'));
+ else {
+ $this->Redirect($referrer);
+ }
}
}
}
@@ -542,4 +559,4 @@
<?php
print($this->FormClose());
}
%%
Save the following as a bookmark in your browser:
%%
javascript:location.href='http://your.site.com/wiki/wikka.php?wakka=BookmarkPage&action=add&uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&wikka_bookmarklet=1'
(Don't forget to change "your.site.com" as appropriate for your installation!)
%%
TODO: Implement as a popup:
%%
post bookmarklet (with popup): javascript:void(open('http://de.lirio.us/rubric/post?uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&when_done=close','Rubric','toolbar=no,width=700,height=325,scrollbars'));
%%
==Other stuff==
- [[BookmarkManagerImportScripts Import scripts]]

==Category==
CategoryDevelopmentDiscussion
CategoryDevelopmentActions
CategoryUserContributions


Revision [16866]

Edited on 2007-05-31 23:26:43 by BrianKoontz [Reverted]
Additions:
%% javascript:location.href='http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks&action=add&uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&wikka_bookmarklet=1'
%%
then go to a webpage and click on the bookmark you just saved.
<<::c::
==Screenshots==
**Main page**
{{image class="left" alt="BookmarkManager main page" url="http://durango.dcccd.edu/images/ss1.jpg"}}
::c::
**Add bookmark page**
{{image class="left" alt="BookmarkManager add page" url="http://durango.dcccd.edu/images/ss2.jpg"}}
::c::
==Design ideas==
- Allow both user-specific and site-wide bookmark access (other than those tagged as "private")
- Allow tagging of objects other than URIs (such as pages)
- Implement as both a handler and an action
- Gordon Luk (creator of [[http://getluky.net/freetag/ Freetag]], the engine behind Bookmark Manager) has some interesting comments about tagging [[http://tagsonomy.com/index.php/interview-with-gordon-luk-freetag/ here]].
- Here's another [[http://www.integratedelf.com/wiki/doku.php?id=wikkawiki:about Wikka tagging project]] using the [[http://getluky.net/freetag Freetag]] engine. It would be useful to investigate the merging of that project with this one to permit page tagging in addition to bookmark tagging.
==Progress reports==
- **Beta test announcement:** Well, after hacking around with Scuttle, I decided it was much too complex for what I had in mind. Nils suggested another project, [[http://getluky.net/freetag/ Freetag]], that seems to provide the basic functionality needed without all of the formatting/presentation overhead. I have a beta up and running [[http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks here]], and would appreciate your comments and suggestions! Keep in mind this is still quite rough around the edges, but I was aiming more towards proof-of-concept. I think something like this would be a cool addition (as a 100% modular plugin) to Wikka...
- Progress is going well. Add/edit/delete capabilities have been added. Two Perl scripts (see below) have been provided that allow you to export your bookmarks from de.lirio.us and import them into BookmarkManager. Beta currently running under Wikka 1.1.6.2-alpha. A page of 25 links (out of 600+) and 100 tags (out of 1200+) renders in approximately 1.1 seconds. A tag seach of 1207 tags takes approximately 1.2 seconds.
- More enhancements and modifications: Numeric tags, link pagination, tag pagination, tag/link editing and deletion.
- More cool stuff: Support for bookmarklets (small Javascript bookmark that immediately saves the page you are viewing and returns to the page), tag links within link descriptors, better use of screen real estate using CSS.
- Tags are now searchable! Searchable titles/descriptions to come...
==TODO==
++Indicates issues that have been addressed++
- ++This code is most likely not yet safe for a production environment!++ //I've worked to secure all data passed in from GET/POST requests, as well as from Wikka itself in some cases. I'm hoping a few more folks have time to look over the code for any obvious security weaknesses.//
- User tags should be displayed as raw tags.
- As a general consideration it would be nice to provide this functionality as a handler.
- ++it would be nice to have a link for the add-form near list yours.++
- ++how about something like my tags / all tags?++ //List all/list yours now provides this functionality//
- what would be usefull: If I tag a link with the name of an existing wiki-page, this link could show up at the end of the page.
- ++Need to fix logic so "your" tags only display "your" links (also, identify tag cloud as "all" or "yours")++
- Optional logic to display tags in a right-justified vertical list instead of a cloud
- ++Paging++, user prefs
- Tag pages as well as links
- ++Please put the wikka_freetag class in a file like Wikka.freetag.class.php in the new \libs directory.++
- ++$freetag_options -> why not use the params provided by wikka ($this->mysql_host, etc.)?++
- ++Create bookmarklets such as these (borrowed from [[de.lirio.us]])++: //Implemented...see below for details//
- ++Provide edit/delete functionality++
- Search functionality (multi-tag, by title/desc)
- Restricting spam. Some ideas: ++Option to allow only registered users to set bookmarks++ //Implemented//; using "captcha" images to preclude automated scripts
- ++It would be nice if tags in each bookmarks were links pointing to the list of bookmarks with the same tag.++
- ++[[http://wikkawiki.org/BookmarkManager?show_comments=1&showall=1#comment_4505 Move markup to their own div tags]]++
- ++[[http://wikkawiki.org/BookmarkManager?show_comments=1&showall=1#comment_4507 Move helper functions, class extensions to libs/ dir]]++
- Fix PHP warnings w/r/t/ null arrays in ksort, min, max, etc.
- The codebase is getting large enough to consider distributing as a tarball/zipfile.
- Split processing of tags and links so they are independent of each other (AJAX??)
- Check/warn on link dupes during add new link operation
- Title and description: What's the diff? More useful to display URL in lieu of one of these?
- Export options: RSS, all to file, to screen (suitable for copy-and-paste), etc.
==Installation==
- Install a copy of [[http://code.google.com/p/freetag/ Freetag]] in **3rdparty/plugins/** (version 0.250 or better) //Note: Rename the installation directory (Freetag-0.250) to 'freetag'//
- Create a MySQL database (I used ##freetag## as the DB name) and use the MySQL dump below to create the necessary tables (such as ##mysql -u root freetag < wikka_freetag.sql##):
**wikka_freetag.sql**
%%(sql)
-- $Id: freetag.sql,v 1.2 2006/05/29 17:11:44 brian Exp brian $
--
-- MySQL dump 9.11
-- Freetag Structure v2.02
--
-- Table structure for table `freetags`
--
DROP TABLE IF EXISTS freetags;
CREATE TABLE freetags (
id int(10) unsigned NOT NULL auto_increment,
tag varchar(30) NOT NULL default '',
raw_tag varchar(50) NOT NULL default '',
PRIMARY KEY (id)
) TYPE=MyISAM;
--
-- Table structure for table `freetagged_objects`
--
DROP TABLE IF EXISTS freetagged_objects;
CREATE TABLE freetagged_objects (
tag_id int(10) unsigned NOT NULL default '0',
tagger_id int(10) unsigned NOT NULL default '0',
object_id int(10) unsigned NOT NULL default '0',
tagged_on datetime NOT NULL default '0000-00-00 00:00:00',
PRIMARY KEY (`tag_id`,`tagger_id`,`object_id`),
KEY `tag_id_index` (`tag_id`),
KEY `tagger_id_index` (`tagger_id`),
KEY `object_id_index` (`object_id`)
) TYPE=MyISAM;
--
-- Table structure for table `freetag_bookmarks`
--
DROP TABLE IF EXISTS freetag_bookmarks;
CREATE TABLE freetag_bookmarks (
bookmark_id int(10) unsigned NOT NULL auto_increment,
title varchar(255) NOT NULL default '',
uri varchar(255) NOT NULL,
description varchar(255) default NULL,
wikka_id varchar(75) NOT NULL,
private tinyint(1) NOT NULL default '0',
PRIMARY KEY (`bookmark_id`),
KEY `bookmark_id_index` (`bookmark_id`)
) TYPE=MyISAM;
--
-- Table structure for table `freetag_wikka_id_map`
--
DROP TABLE IF EXISTS freetag_wikka_id_map;
CREATE TABLE freetag_wikka_id_map (
tagger_id int(10) unsigned NOT NULL AUTO_INCREMENT,
wikka_id varchar(75) NOT NULL default '',
PRIMARY KEY(`tagger_id`)
) TYPE=MyISAM;
%%
- Save the following file as ##actions/bookmarks.php##:
**bookmarks.php**
%%(php)
<?php
/**
* Bookmark manager with tagging
*
* Allows users (registered or not) to tag objects such as
* bookmarks and display linked tags in the spirit of
* {@link http://de.licio.us} and {@link http://de.lirio.us}.
*
* Usage: {{bookmarks}}
*
* Credits:
* Gordon Luk's Freetag for PHP4 {@link http://www.getluky.net/freetag}
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public Licence for more details:
*
* http://www.gnu.org/copyleft/gpl.html
*
*
* @author {@link http://wikkawiki.org/BrianKoontz Brian Koontz} <brian@pongonova.net>
* @copyright Copyright (c) 2006, Brian Koontz <brian@pongonova.net>
* @name BookmarkManager
* @package Actions
* @filesource
* @license http://www.gnu.org/copyleft/gpl.html
* @since Wikka 1.1.6.1
* @version $Id: bookmarks.php,v 1.8 2006/08/10 04:03:38 brian Exp brian $
*
*/
// Check for freetag class and helper libraries
include_once('libs/Wikka.freetag.class.php');
include_once('libs/Wikka.freetag.lib.php');
/********************************************************************/
/* DB connection params */
/********************************************************************/
$freetag_options = array(
'db_user' => $this->config['mysql_user'],
'db_pass' => $this->config['mysql_password'],
'db_host' => $this->config['mysql_host'],
'db_name' => 'freetag',
'PCONNECT' => true,
);
$freetag = new wikka_freetag($freetag_options);
$wikka_id = $this->GetUserName();
$tagger_id = wikka_id_to_tagger_id($wikka_id, $this, $freetag_options);
$all_obj_count = count_objects(NULL, $this, $freetag_options);
$your_obj_count = count_objects($wikka_id, $this, $freetag_options);
$search_type = "";
if(isset($_REQUEST['action'])) {
if($_REQUEST['action']=="list_yours") {
$search_type = "search_yours";
} else {
$search_type = "search_all";
}
}
echo "<a href=\"".$this->href()."&action=list_all"."\">List all</a> (".$all_obj_count.") | ".
"<a href=\"".$this->href()."&action=list_yours"."\">List yours</a> (".$your_obj_count.") | ".
"<a href=\"".$this->href()."&action=add"."\">Add</a>".
$this->FormOpen().
"<input type=\"hidden\" name=\"action\" value=\"".$search_type."\" />".
"Search: ".
"<input name=\"search\" size=\"20\" />".
"<input type=\"radio\" name=\"search_type\" value=\"tags\" checked>Tags".
"<input type=\"radio\" name=\"search_type\" value=\"title_desc\">Title/Desc".
$this->FormClose();
if(!isset($_REQUEST['action'])) {
// Display all tags at start of session
$this->Redirect($this->href()."&action=list_all");
}
// Search by tags, titles, or descriptions
// Action name: search
// Search field name: search
// Radio buttons: search_type: "tags" or "title_desc"
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="search_yours") || ($_REQUEST['action']=="search_all")) {
if(isset($_REQUEST['search_type']) &&
($_REQUEST['search_type']=="tags") &&
isset($_REQUEST['search'])) {
if($_REQUEST['action'] == "search_yours") {
$this->Redirect($this->href()."&search_term=".$_REQUEST['search']."&action=list_yours");
} else {
$this->Redirect($this->href()."&search_term=".$_REQUEST['search']."&action=list_all");
}
}
}
// Add new entry to DB
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="add")) {
$values = array();
// Check for request from a bookmarklet...check to see if user
// is logged in...if not, redirect to login page and come back
// here when done
if(isset($_REQUEST['wikka_bookmarklet'])) {
$values['uri'] = mysql_real_escape_string($_REQUEST['uri']);
if(isset($_REQUEST['title'])) {
$values['title'] = mysql_real_escape_string($_REQUEST['title']);
}
$values['referrer'] = $_SERVER['HTTP_REFERER'];
$values['when_done'] = "add_link";
save_to_current_session($values);
//session_write_close();
if(!$this->GetUser()) {
$_SESSION['go_back'] = $this->Href(null, $this->MiniHref()."&action=add");
//session_write_close();
$this->Redirect($this->config['base_url']."UserSettings");
}
}
// The next clause checks to see if we're returning from a
// login request while trying to execute a bookmarklet
if(isset($_SESSION['when_done']) && ($_SESSION['when_done']=="add_link")) {
$values['uri'] = $_SESSION['uri'];
$values['title'] = $_SESSION['title'];
$_SESSION['when_done'] = "return";
//session_write_close();
}
if(trim($_REQUEST['tags']) != "") {
// Private bookmark?
$private = 0;
if(strpos(trim($_REQUEST['tags']), "@private") !== false)
$private = 1;

// Converts numeric tag "123" to "123_" to facilitate
// alphanumeric sorting (otherwise, PHP converts string to
// true integer).
$tags = preg_split('/\s+/', $_REQUEST['tags'], -1, PREG_SPLIT_NO_EMPTY);
$tags = preg_replace('/^([0-9]+)$/', "$1_", $tags);
$tags = implode(" ", $tags);
$this->Query("insert ".$freetag_options['db_name'].".freetag_bookmarks set ".
"title = '".mysql_real_escape_string($_REQUEST['title'])."', ".
"uri = '".mysql_real_escape_string($this->cleanUrl($_REQUEST['uri']))."', ".
"wikka_id = '".mysql_real_escape_string($wikka_id)."', ".
"private = '".$private."', ".
"description = '".mysql_real_escape_string($_REQUEST['desc'])."';");
$last_id = $this->LoadSingle("select last_insert_id();");
$freetag->tag_object($tagger_id, $last_id['last_insert_id()'], $tags);
// Check to see if it's time to return to the page from which
// the bookmarklet was called
if(isset($_SESSION['when_done']) && ($_SESSION['when_done']=="return")) {
if(isset($_SESSION['referrer'])) {
$referrer = $_SESSION['referrer'];
unset($_SESSION['go_back']);
unset($_SESSION['uri']);
unset($_SESSION['title']);
unset($_SESSION['referrer']);
unset($_SESSION['when_done']);
$this->Redirect($referrer);
}
}
// Otherwise, go back to tag/link list view
$this->Redirect($this->href()."&action=list_yours");
} else {
// Display add form
print($this->FormOpen());
?>
<input type="hidden" name="action" value="add" />
<table>
<tr>
<td align="right">Title:</td>
<td><input name="title" size="40" value="<?php echo $values['title']; ?>" /></td>
</tr>
<tr>
<td align="right">URI:</td> <td><input name="uri" size="40" value="<?php echo $values['uri']; ?>" /></td> </tr>
<tr>
<td align="right">Description:</td>
<td><input name="desc" size="40"/></td>
</tr>
<tr>
<td align="right"></td>
<td><?php echo $this->Format("Use blank space between tags, include \"@private\" for private bookmark"); ?></td>
</tr>
<tr>
<td align="right">Tags:</td>
<td><input name="tags" size="40"/></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Add" size="40" /></td>
</tr>
</table>
<?php
print($this->FormClose());
}
}
// Edit entry
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="edit") && (isset($_REQUEST['object_id']))) {
$object_id = mysql_real_escape_string($_REQUEST['object_id']);
$obj = $this->LoadSingle("select bookmark_id,title,uri,description,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$object_id.";");
// Is the logged-in user the owner?
if($wikka_id != $obj['wikka_id']) {
$this->Redirect($this->href()."&action=list_all");
}
if(isset($_REQUEST['modify']) && trim($_REQUEST['tags']) != "") {
// Delete all tags, then re-tag
$freetag->delete_all_object_tags_for_user($tagger_id,$object_id);
// Private bookmark?
$private = 0;
if(strpos(trim($_REQUEST['tags']), "@private") !== false)
$private = 1;
$this->Query("update ".$freetag_options['db_name'].".freetag_bookmarks set ".
"title = '".mysql_real_escape_string($_REQUEST['title'])."', ".
"uri = '".mysql_real_escape_string($this->cleanUrl($_REQUEST['uri']))."', ".
"wikka_id = '".mysql_real_escape_string($wikka_id)."', ".
"private = '".$private."', ".
"description = '".mysql_real_escape_string($_REQUEST['desc'])."' ".
"where bookmark_id = ".mysql_real_escape_string($object_id)." ".
"limit 1;");
$freetag->tag_object($tagger_id, $obj['bookmark_id'], $_REQUEST['tags']);
$this->Redirect($this->href()."&action=list_yours");
}
// Display add form
print($this->FormOpen());
?>
<input type="hidden" name="action" value="edit" />
<input type="hidden" name="modify" value="yes" />
<input type="hidden" name="object_id" value="<?php print $object_id ?>" />
<table>
<tr>
<td align="right">Title:</td>
<td><input name="title" size="40" value="<?php print $obj['title']; ?>"/></td>
</tr>
<tr>
<td align="right">URI:</td>
<td><input name="uri" size="40" value="<?php print $obj['uri']; ?>"/></td>
</tr>
<tr>
<td align="right">Description:</td>
<td><input name="desc" size="40" value="<?php print $obj['description']; ?>"/></td>
</tr>
<tr>
<td align="right"></td>
<td><?php echo $this->Format("Use blank space between tags, include \"@private\" for private bookmark"); ?></td>
</tr>
<tr>
<td align="right">Tags:</td>
<?php
$tags = $freetag->get_tags_on_object($object_id);
$taglist = "";
foreach($tags as $idx=>$res) {
$taglist .= trim($res['raw_tag'])." ";
}
?>
<td><input name="tags" size="40" value="<?php print $taglist?>"/></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Edit" size="40" /></td>
</tr>
</table>
<?php
print($this->FormClose());
}
// Delete entry
if(isset($_REQUEST['action']) && $_REQUEST['action']=="delete") {
$object_id = mysql_real_escape_string($_REQUEST['object_id']);
$obj = $this->LoadSingle("select bookmark_id,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".mysql_real_escape_string($object_id).";");
// Is the logged-in user the owner?
if($wikka_id != $obj['wikka_id']) {
$this->Redirect($this->href()."&action=list_all");
}
// Delete all tags first...
$freetag->delete_all_object_tags_for_user($tagger_id,$object_id);
// ...then delete object
$this->Query("delete from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".mysql_real_escape_string($object_id)." limit 1;");
$this->Redirect($this->href()."&action=list_yours");
}
// List all bookmarks (ordered by most recent)
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="list_all")) {
$offset = 0;
if(isset($_REQUEST['offset'])) {
$offset = mysql_real_escape_string($_REQUEST['offset']);
}
$limit = 25;
if(isset($_REQUEST['limit'])) {
$limit = mysql_real_escape_string($_REQUEST['limit']);
}
$tag_offset = 0;
if(isset($_REQUEST['tag_offset'])) {
$tag_offset = mysql_real_escape_string($_REQUEST['tag_offset']);
}
$tag_limit = 100;
if(isset($_REQUEST['tag_limit'])) {
$tag_limit = mysql_real_escape_string($_REQUEST['tag_limit']);
}
// Output tag cloud
$url_opts = $this->href()."&action=list_all";
$search_term = NULL;
$header = "All tags";
if(isset($_REQUEST['search_term'])) {
$search_term = mysql_real_escape_string($_REQUEST['search_term']);
$header = "All tags by search";
}
output_tag_cloud($freetag,$url_opts,NULL,$header,$tag_offset,$tag_limit,$search_term);
$ids = "";
$tag = "";
if(isset($_REQUEST['tag']) && $_REQUEST['tag'] != "") {
$ids = $freetag->get_most_recent_objects(NULL,$_REQUEST['tag'],$offset,$limit);
$tag = mysql_real_escape_string($_REQUEST['tag']);
} else {
$ids = $freetag->get_most_recent_objects(NULL,NULL,$offset,$limit);
}
foreach($ids as $key=>$val) {
$tags = $freetag->get_tags_on_object($val['object_id']);
$obj = $this->LoadSingle("select title,uri,description,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$val['object_id']." and private = 0;");
if(!isset($obj)) {
continue;
}
print "<div class=\"bookmark_block\">\n";
print "<div class=\"bookmark_title\"><a href=\"".$obj['uri']."\">".$obj['title']."</a></div>\n";
print "<div class=\"bookmark_box\"><div class=\"bookmark_desc\">".($obj['description'] ? $obj['description'] : " ")."</div>\n";
$taglist = "";
$link = $url_opts."&tag_offset=".$tag_offset."&tag_limit=".$tag_limit;
foreach($tags as $idx=>$res) {
$temp_tag = trim($res['tag']);
$taglist .= "<a href=\"".$link."&tag=".$temp_tag."\">".$temp_tag."</a> ";
}
print $taglist."by ".$obj['wikka_id']." (created: ".$val['tagged_on'].")\n";
//print "</div><div class=\"clear\"> </div><hr/>";
print "</div></div>\n";
}
// Display pagination links
$obj_count = 0;
$tag_url = "";
if($tag != "") {
$obj_count = count_tagged_objects(NULL, $tag, $this, $freetag_options, $freetag);
$tag_url = "&tag=".$tag;
} else {
$obj_count = count_objects(NULL, $this, $freetag_options);
}
$prev_offset = $offset - $limit;
$prev_url = "";
if($prev_offset < 0) {
$prev_offset = 0;
}
if($offset > 0) {
$prev_url = "<a href=\"".$this->href()."&action=list_all&offset=".$prev_offset."&limit=".$limit.$tag_url."\"><<</a>";
}
$next_offset = $offset + $limit;
$next_url = "";
if($next_offset < $obj_count - 1) {
$next_url = "<a href=\"".$this->href()."&action=list_all&offset=".$next_offset."&limit=".$limit.$tag_url."\">>></a>";
}
print "<div class=\"pagination\">";
print $prev_url."  ".$next_url;
print "</div>";
}
// List user bookmarks (ordered by most recent)
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="list_yours")) {
$offset = 0;
if(isset($_REQUEST['offset'])) {
$offset = mysql_real_escape_string($_REQUEST['offset']);
}
$limit = 25;
if(isset($_REQUEST['limit'])) {
$limit = mysql_real_escape_string($_REQUEST['limit']);
}
$tag_offset = 0;
if(isset($_REQUEST['tag_offset'])) {
$tag_offset = mysql_real_escape_string($_REQUEST['tag_offset']);
}
$tag_limit = 100;
if(isset($_REQUEST['tag_limit'])) {
$tag_limit = mysql_real_escape_string($_REQUEST['tag_limit']);
}
// Output tag cloud
$url_opts = $this->href()."&action=list_yours";
$search_term = NULL;
$header = "Your tags";
if(isset($_REQUEST['search_term'])) {
$search_term = mysql_real_escape_string($_REQUEST['search_term']);
$header = "Your tags by search";
}
output_tag_cloud($freetag,$url_opts,$tagger_id,$header,$tag_offset,$tag_limit,$search_term);
$ids = "";
$tag = "";
if(isset($_REQUEST['tag']) && $_REQUEST['tag'] != "") {
$ids = $freetag->get_most_recent_objects($tagger_id, $_REQUEST['tag'], $offset, $limit);
$tag = mysql_real_escape_string($_REQUEST['tag']);
} else {
$ids = $freetag->get_most_recent_objects($tagger_id, NULL, $offset, $limit);
}
foreach($ids as $key=>$val) {
$tags = $freetag->get_tags_on_object($val['object_id']);
$obj = $this->LoadSingle("select title,uri,description,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$val['object_id'].";");
print "<div class=\"bookmark_block\">\n";
print "<div class=\"bookmark_title\"><a href=\"".$obj['uri']."\">".$obj['title']."</a></div>\n";
print "<div class=\"bookmark_box\"><div class=\"bookmark_desc\">".($obj['description'] ? $obj['description'] : " ")."</div>\n";
print " <a href=\"".$this->href()."&action=edit&object_id=".$val['object_id']."\">edit</a>";
print "|";
print "<a href=\"".$this->href()."&action=delete&object_id=".$val['object_id']."\">delete</a><br/>";
$taglist = "";
$link = $url_opts."&tag_offset=".$tag_offset."&tag_limit=".$tag_limit;
foreach($tags as $idx=>$res) {
$normtag = trim($res['tag']);
$rawtag = trim($res['raw_tag']);
$taglist .= "<a href=\"".$link."&tag=".$normtag."\">".$rawtag."</a> ";
}
print $taglist."by ".$obj['wikka_id']." (created: ".$val['tagged_on'].")\n";
//print "<div class=\"clear\"> </div><hr/>";
print "</div></div>\n";
}
// Display pagination links
$obj_count = 0;
$tag_url = "";
if($tag != "") {
$obj_count = count_tagged_objects($wikka_id, $tag, $this, $freetag_options, $freetag);
$tag_url = "&tag=".$tag;
} else {
$obj_count = count_objects($wikka_id, $this, $freetag_options);
}
$prev_offset = $offset - $limit;
$prev_url = "";
if($prev_offset < 0) {
$prev_offset = 0;
}
if($offset > 0) {
$prev_url = "<a href=\"".$this->href()."&action=list_yours&offset=".$prev_offset."&limit=".$limit.$tag_url."\"><<</a>";
}
$next_offset = $offset + $limit;
$next_url = "";
if($next_offset < $obj_count - 1) {
$next_url = "<a href=\"".$this->href()."&action=list_yours&offset=".$next_offset."&limit=".$limit.$tag_url."\">>></a>";
}
print "<div class=\"center\">";
print $prev_url."  ".$next_url;
print "</div>";
}
?>
%%
- Save the following file as ##libs/Wikka.freetag.lib.php##:
**Wikka.freetag.lib.php**
%%(php)
<?php
/**
* Wikka.freetag.lib.php
*
* Set of helper classes for BookmarkManager. The reason these
* aren't included in the Freetag extension class
* (Wikka.freetag.class.php) is that Wikka functions defined
* outside the scope of the class aren't accessible from within
* the class, and most of these helper functions use both Freetag
* and Wikka functions.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public Licence for more details:
*
* http://www.gnu.org/copyleft/gpl.html
*
*
* @author {@link http://wikkawiki.org/BrianKoontz Brian Koontz} <brian@pongonova.net>
* @copyright Copyright (c) 2006, Brian Koontz <brian@pongonova.net>
* @name Wikka.freetag.lib.php
* @package Actions {@link http://www.wikkawiki.org/BookmarkManager}
* @filesource
* @license http://www.gnu.org/copyleft/gpl.html
* @since Wikka 1.1.6.1
* @version $Id: Wikka.freetag.lib.php,v 1.3 2006/08/10 04:03:17 brian Exp brian $
*
*/
/*********************************************************************/
/* Helper functions */
/*********************************************************************/
/**
* wikka_id_to_tagger_id()
*
* Converts Wikka id (returned from GetUserName()) to
* tagger_id. Adds Wikka id to table if not already present.
* Maps NULL -> NULL.
*
*/
function wikka_id_to_tagger_id ($wikka_id, $obj, $freetag_options) {
if(!isset($wikka_id)) {
return NULL;
}
$wikka_id = mysql_real_escape_string($wikka_id);
$res = $obj->LoadSingle("select tagger_id from ".$freetag_options['db_name'].".freetag_wikka_id_map where wikka_id = '".$wikka_id."';");
$tagger_id = $res['tagger_id'];
if(!$tagger_id) {
$obj->Query("insert ".$freetag_options['db_name'].".freetag_wikka_id_map set "."wikka_id = '".$wikka_id."';");
$res = $obj->LoadSingle("select last_insert_id();");
$tagger_id = $res['last_insert_id()'];
}
return $tagger_id;
}
/********************************************************************/
/**
* output_tag_cloud()
*
* Create a cloud of tags!
*
* NOTES:
* Set $tagger_id to NULL for all tags (except those marked
* private)
*
* If $search_term is not NULL, $tag_offset will return the offset
* of the first matching term
*/
function output_tag_cloud($freetag,$tag_page_url,$tagger_id=NULL,$header=NULL,$tag_offset=0,$tag_limit=100,$search_term=NULL) {
// Output tag cloud
$tag_count = $freetag->count_unique_tags($tagger_id,$search_term);
print "<div class='bookmark_floatr'>";
if($header) {
print $header." (".$tag_count."):<br/>";
}
print $freetag->get_tag_cloud_html_with_limits($tag_offset,$tag_limit,10,20,'px','cloud_tag',$tag_page_url,$tagger_id,$search_term);
// Display pagination links
$tag_url = "";
$prev_offset = $tag_offset - $tag_limit;
$prev_url = "";
if($prev_offset < 0) {
$prev_offset = 0;
}
$search_tag = "";
if(isset($search_term)) {
$search_tag = "&search_term=".$search_term;
}
if($tag_offset > 0) {
$prev_url = "<a href=\"".$tag_page_url."&tag_offset=".$prev_offset."&tag_limit=".$tag_limit.$search_tag."\"><<</a>";
}
$next_offset = $tag_offset + $tag_limit;
$next_url = "";
if($next_offset < $tag_count - 1) {
$next_url = "<a href=\"".$tag_page_url."&tag_offset=".$next_offset."&tag_limit=".$tag_limit.$search_tag.$tag_url."\">>></a>";
}
print "<div class=\"pagination\">";
print $prev_url."  ".$next_url;
print "</div>";
print "</div>";
// print "<div class=\"clear\"> </div>";
}
/********************************************************************/
/**
* count_objects()
*
* Returns a count of objects (bookmarks, links, etc.)
*
* NOTE: Set $wikka_id to NULL for all objects (except those marked
* private)
*/
function count_objects($wikka_id, $obj, $freetag_options) {
if(isset($wikka_id)) {
$wikka_sql = "AND wikka_id = '".mysql_real_escape_string($wikka_id)."'";
} else {
$wikka_sql = "AND private = 0";
}
$res = $obj->LoadSingle("select COUNT(*) as count from ".$freetag_options['db_name'].".freetag_bookmarks where 1 ".$wikka_sql.";");
$count = $res['count'];
return $count;
}
/********************************************************************/
/**
* count_tagged_objects()
*
* Returns a count of tagged objects (bookmarks, links, etc.)
*
* NOTE: Set $wikka_id to NULL for all taggedobjects regardless of
* owner (except those marked private)
*/
function count_tagged_objects($wikka_id, $tag, $obj, $freetag_options, $freetag) {
$num_objs = count_objects($wikka_id, $obj, $freetag_options);
$tagger_id = wikka_id_to_tagger_id($wikka_id, $obj, $freetag_options);
$ids = $freetag->get_most_recent_objects($tagger_id, $tag, 0, $num_objs);
return count($ids);
}
/********************************************************************/
/**
* save_to_current_session()
*
* Save various fields to the current session
*
* Input: Associative array of session tags->values
*
* Return: Name of session
*
*/
function save_to_current_session($values) {
foreach($values as $tag=>$value) {
$_SESSION[$tag] = $value;
}
return $_SESSION['name'];
}
/********************************************************************/
?>
%%
- Save the following file as ##libs/Wikka.freetag.class.php##:
**Wikka.freetag.class.php**
%%(php)
<?php
/*******************************************************************
* $Id: Wikka.freetag.class.php,v 1.8 2006/08/10 04:03:28 brian Exp brian $
*
* Wikka.freetag.class.php
*
* Extension to Gordon Luk's Freetag for PHP4 (see credits) for
* use with Wikka Bookmark action/handler
*
* Credits:
* Gordon Luk's Freetag for PHP4 (http://www.getluky.net/freetag)
*
* Author:
* Copyright (c) 2006 Brian Koontz <brian@pongonova.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public Licence for more details:
*
* http://www.gnu.org/copyleft/gpl.html
*
*********************************************************************/
// Check for freetag library
$freetag_lib = '3rdparty/plugins/freetag/freetag.class.php';
if(!is_file($freetag_lib)) {
print("<br/><br/><div class=\"error\">".$this->Format("Can't find $freetag_lib!")."<br/><br/>\n");
die();
}
include_once($freetag_lib);
/********************************************************************/
/* Freetag subclass (defines additional support methods) */
/********************************************************************/
class wikka_freetag extends freetag {
function wikka_freetag($freetag_options) {
parent::freetag($freetag_options);
$this->_normalized_valid_chars = '@_a-zA-Z0-9';
}

/*******************************************************************
* get_tag_cloud_html_with_limits
*
* Extension to get_tag_cloud_html() that permits specifying
* an offset and limit (useful for presenting more tags than
* can be displayed at one time).
*
* See get_tag_cloud_html() comments for description of this
* function.
*
* @param int Specify starting record (default: 0). Will be set to
* offset of first matching term if $search_term is not NULL
* @param int The maximum number of tags to return. (default: 100)
* @param int The minimum font size in the cloud. (default: 10)
* @param int The maximum number of tags to return. (default: 20)
* @param string The "units" for the font size (i.e. 'px', 'pt',
* 'em') (default: px)
* @param string The class to use for all spans in the cloud.
* (default: cloud_tag)
* @param string The tag page URL (default: /tag/)
* @param int The tagger ID (default: NULL)
* @param string Optional search term (default: NULL)
*
* @return string Returns an HTML snippet that can be used
* directly as a tag cloud.
*/
function get_tag_cloud_html_with_limits($offset = 0, $limit = 100, $min_font_size = 10, $max_font_size = 20, $font_units = 'px', $span_class = 'cloud_tag', $tag_page_url = '/tag/', $tagger_id = NULL, $search_term = NULL) {
$tag_list = $this->get_tag_cloud_tags_with_limits($offset, $limit, $tagger_id,$search_term);
// Get the maximum qty of tagged objects in the set
$max_qty = max(array_values($tag_list));
// Get the min qty of tagged objects in the set
$min_qty = min(array_values($tag_list));
// For ever additional tagged object from min to max, we add
// $step to the font size.
$spread = $max_qty - $min_qty;
if (0 == $spread) { // Divide by zero
$spread = 1;
}
$step = ($max_font_size - $min_font_size)/($spread);
// Since the original tag_list is alphabetically ordered,
// we can now create the tag cloud by just putting a span
// on each element, multiplying the diff between min and qty
// by $step.
$cloud_html = '';
$cloud_spans = array();
foreach ($tag_list as $tag => $qty) {
//if(strpos($tag, "private") !== false) continue;
$size = $min_font_size + ($qty - $min_qty) * $step;
$search_tag = "";
if(isset($search_term)) {
$search_tag = "&search_term=".$search_term;
}
$cloud_span[] = '<span class="' . $span_class . '" style="font-size: '. $size . $font_units . '"><a href="'.$tag_page_url . "&tag_offset=".$offset."&tag_limit=".$limit."&tag=".$tag.$search_tag. '">' . htmlspecialchars(stripslashes($tag)) . '</a></span>';
}
// Remove <br/> from the following line to get a "tag
// cloud" rather than a "tag list"
$cloud_html = join("<br/>\n ", $cloud_span);
return $cloud_html;
}
/*
* get_tag_cloud_tags_with_limits
*
* Extension to get_tag_cloud_tags() that permits specifying
* an offset and limit (useful for presenting more tags than
* can be displayed at one time). Also, tags are returned
* in true alphanumeric sequence across the entire record set,
* rather than across the most popular tags.
*
* See get_tag_cloud_tags() comments for description of this
* function.
* @param int Specify starting record (default: 0). Will be set to
* offset of first matching tag if $search_term is not NULL.
* @param int The maximum number of tags to return (default: 100)
* @param int Tag owner (default: NULL)
* @param string Optional search term (default: NULL)
*
* @return array Returns an array where the keys are normalized
* tags, and the
* values are numeric quantity of objects tagged with that tag.
*/
function get_tag_cloud_tags_with_limits($offset = 0, $limit = 100, $tagger_id = NULL, $search_term = NULL) {
$db = $this->db;
$limit_sql = "LIMIT ".$offset.", ".$limit;
if(isset($tagger_id) && ($tagger_id > 0)) {
$tagger_sql = "AND tagger_id = $tagger_id";
} else {
$tagger_sql = "";
}
$search_sql = "";
if(isset($search_term)) {
$search_sql = "AND tag LIKE \"".$search_term."%\"";
//$limit_sql = "LIMIT ".$limit;
//$offset = 0;
}
$prefix = $this->_table_prefix;
$sql = "SELECT tag, COUNT(object_id) AS quantity
FROM ${prefix}freetags INNER JOIN
${prefix}freetagged_objects
ON (${prefix}freetags.id = tag_id)
WHERE 1
$tagger_sql
$search_sql
GROUP BY tag
ORDER BY tag ASC
$limit_sql
";
$rs = $db->Execute($sql) or die("Syntax Error: $sql");
while(!$rs->EOF) {
$retarr[$rs->fields['tag']] = $rs->fields['quantity'];
$rs->MoveNext();
if($search_term) {
//$offset++;
}
}
ksort($retarr);
return $retarr;
}
/**
* count_unique_tags
* Returns the total number of unique tags in the system.
*
* @param int The unique ID of the person to restrict results to.
* @param string Optional search string (default: NULL)
*
* @return int Returns the count
*/
function count_unique_tags($tagger_id = NULL, $search_term = NULL) {
$db = $this->db;
if(isset($tagger_id) && ($tagger_id > 0)) {
$tagger_sql = "AND tagger_id = $tagger_id";
} else {
$tagger_sql = "";
}
$search_sql = "";
if(isset($search_term)) {
$search_sql = "AND tag LIKE \"".$search_term."%\"";
//$limit_sql = "LIMIT ".$limit;
//$offset = 0;
}
$prefix = $this->_table_prefix;
$sql = "SELECT DISTINCT tag, tag_id, COUNT(*) as count
FROM ${prefix}freetags INNER JOIN ${prefix}freetagged_objects ON (id = tag_id)
$tagger_sql
$search_sql
GROUP BY NULL
";
$rs = $db->Execute($sql) or die("Syntax Error: $sql");
if(!$rs->EOF) {
return $rs->fields['count'];
}
return false;
}
}
/********************************************************************/
?>
%%
- Adjust the DB connection parameters for your own installation (only necessary if you deviate from the DB instructions above)

- Create the CSS stylesheet for the action, ##css/bookmarkmgr.css##:
%%(php)
.bookmark_floatr {
float: right;
text-align: left;
width: 10%;
margin-left: 15px;
padding: 4px;
background: #EEE;
border: 1px solid #CCC;
line-height: 95%;
}
.bookmark_box {
width: 85%;
font-size: 85%;
padding: 0px 5px;
color: #666;
background: #EEE;
border: 1px solid #CCC;
}
.bookmark_block {
width: 85%;
border-bottom: 3px solid #DDD;
padding: 8px;
}
.bookmark_desc {
font-size: 100%;
font-weight: bold;
margin: 0px;
padding: 0px;
}
.bookmark_title {
font-size: 150%;
font-weight: bold;
padding: 5px 0px;
}
.pagination {
text-align: center;
margin: 20px auto;
}
%%
- In order to use the stylesheet, add the following lines to the files indicated (click [[http://wush.net/trac/wikka/ticket/246 here]] for a justification as to why these changes are necessary to the core):
**wikka.config.php:**
%%(php)
'additional_stylesheets' => 'bookmarkmgr.css',
%%
**actions/header.php:**
%%(php)
// Locate this line...
<link rel="stylesheet" type="text/css" href="css/<?php echo $this->GetConfigValue("stylesheet") ?>" />
// ...and add the following codeblock:
<?php
if($this->GetConfigValue("additional_stylesheets") != NULL) {
$sheets = preg_split("/[\s]+/", $this->GetConfigValue("additional_stylesheets"));
foreach($sheets as $sheet) {
echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"css/".$sheet."\" />\n";
}
}
?>
%%
- Add the ""{{bookmarks}}"" action code to a new page

==Bookmarklet support==
BookmarkManager now supports "bookmarklets," which is a small snippet of Javascript saved as a browser bookmark that allows you to bookmark a webpage "on the fly." The following patch must be applied to ##actions/usersettings.php## if you want to restrict bookmarklets to registered users (recommended):
%%(sh)
===================================================================
RCS file: RCS/usersettings.php,v
retrieving revision 1.1
diff -u -r1.1 usersettings.php
--- usersettings.php 2006/06/18 06:03:03 1.1
+++ usersettings.php 2006/06/24 06:40:13
@@ -1,5 +1,7 @@
<?php
/**
+ * $Id: usersettings.php,v 1.5 2006/06/24 06:40:07 brian Exp brian $
+ *
* Display a form to register, login and change user settings.
*
* @package Actions
@@ -334,6 +336,13 @@
// is user trying to log in or register?
if (isset($_POST['action']) && ($_POST['action'] == 'login'))
{
+ // Login request was redirected from elsewhere...let's make
+ // sure to go back if requested by setting $referrer
+ $referrer = null;
+ if(isset($_SESSION['go_back'])) {
+ $referrer = $_SESSION['go_back'];
+ }
+
// if user name already exists, check password
if (isset($_POST['name']) && $existingUser = $this->LoadUser($_POST['name']))
{
@@ -349,7 +358,11 @@
break;
default:
$this->SetUser($existingUser);
- $this->Redirect($this->href());
+ if($referrer == null)
+ $this->Redirect($this->href());
+ else {
+ $this->Redirect($referrer);
+ }
}
}
else // otherwise, proceed to registration
@@ -417,7 +430,11 @@

// log in
$this->SetUser($this->LoadUser($name));
- $this->Redirect($this->href('', '', 'registered=true'));
+ if($referrer == null)
+ $this->Redirect($this->href('', '', 'registered=true'));
+ else {
+ $this->Redirect($referrer);
+ }
}
}
}
@@ -542,4 +559,4 @@
<?php
print($this->FormClose());
}
%%
Save the following as a bookmark in your browser:
%%
javascript:location.href='http://your.site.com/wiki/wikka.php?wakka=BookmarkPage&action=add&uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&wikka_bookmarklet=1'
(Don't forget to change "your.site.com" as appropriate for your installation!)
%%
TODO: Implement as a popup:
%%
post bookmarklet (with popup): javascript:void(open('http://de.lirio.us/rubric/post?uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&when_done=close','Rubric','toolbar=no,width=700,height=325,scrollbars'));
%%
==Other stuff==
- [[BookmarkManagerImportScripts Import scripts]]

==Category==
CategoryDevelopmentDiscussion
CategoryDevelopmentActions
CategoryUserContributions
Deletions:
%% javascript:location.href='http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks


Revision [16664]

Edited on 2007-05-31 10:32:45 by IjzQ5y [Reverted]
Additions:
%% javascript:location.href='http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks
Deletions:
%% javascript:location.href='http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks&action=add&uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&wikka_bookmarklet=1'
%%
then go to a webpage and click on the bookmark you just saved.
<<::c::
==Screenshots==
**Main page**
{{image class="left" alt="BookmarkManager main page" url="http://durango.dcccd.edu/images/ss1.jpg"}}
::c::
**Add bookmark page**
{{image class="left" alt="BookmarkManager add page" url="http://durango.dcccd.edu/images/ss2.jpg"}}
::c::
==Design ideas==
- Allow both user-specific and site-wide bookmark access (other than those tagged as "private")
- Allow tagging of objects other than URIs (such as pages)
- Implement as both a handler and an action
- Gordon Luk (creator of [[http://getluky.net/freetag/ Freetag]], the engine behind Bookmark Manager) has some interesting comments about tagging [[http://tagsonomy.com/index.php/interview-with-gordon-luk-freetag/ here]].
- Here's another [[http://www.integratedelf.com/wiki/doku.php?id=wikkawiki:about Wikka tagging project]] using the [[http://getluky.net/freetag Freetag]] engine. It would be useful to investigate the merging of that project with this one to permit page tagging in addition to bookmark tagging.
==Progress reports==
- **Beta test announcement:** Well, after hacking around with Scuttle, I decided it was much too complex for what I had in mind. Nils suggested another project, [[http://getluky.net/freetag/ Freetag]], that seems to provide the basic functionality needed without all of the formatting/presentation overhead. I have a beta up and running [[http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks here]], and would appreciate your comments and suggestions! Keep in mind this is still quite rough around the edges, but I was aiming more towards proof-of-concept. I think something like this would be a cool addition (as a 100% modular plugin) to Wikka...
- Progress is going well. Add/edit/delete capabilities have been added. Two Perl scripts (see below) have been provided that allow you to export your bookmarks from de.lirio.us and import them into BookmarkManager. Beta currently running under Wikka 1.1.6.2-alpha. A page of 25 links (out of 600+) and 100 tags (out of 1200+) renders in approximately 1.1 seconds. A tag seach of 1207 tags takes approximately 1.2 seconds.
- More enhancements and modifications: Numeric tags, link pagination, tag pagination, tag/link editing and deletion.
- More cool stuff: Support for bookmarklets (small Javascript bookmark that immediately saves the page you are viewing and returns to the page), tag links within link descriptors, better use of screen real estate using CSS.
- Tags are now searchable! Searchable titles/descriptions to come...
==TODO==
++Indicates issues that have been addressed++
- ++This code is most likely not yet safe for a production environment!++ //I've worked to secure all data passed in from GET/POST requests, as well as from Wikka itself in some cases. I'm hoping a few more folks have time to look over the code for any obvious security weaknesses.//
- User tags should be displayed as raw tags.
- As a general consideration it would be nice to provide this functionality as a handler.
- ++it would be nice to have a link for the add-form near list yours.++
- ++how about something like my tags / all tags?++ //List all/list yours now provides this functionality//
- what would be usefull: If I tag a link with the name of an existing wiki-page, this link could show up at the end of the page.
- ++Need to fix logic so "your" tags only display "your" links (also, identify tag cloud as "all" or "yours")++
- Optional logic to display tags in a right-justified vertical list instead of a cloud
- ++Paging++, user prefs
- Tag pages as well as links
- ++Please put the wikka_freetag class in a file like Wikka.freetag.class.php in the new \libs directory.++
- ++$freetag_options -> why not use the params provided by wikka ($this->mysql_host, etc.)?++
- ++Create bookmarklets such as these (borrowed from [[de.lirio.us]])++: //Implemented...see below for details//
- ++Provide edit/delete functionality++
- Search functionality (multi-tag, by title/desc)
- Restricting spam. Some ideas: ++Option to allow only registered users to set bookmarks++ //Implemented//; using "captcha" images to preclude automated scripts
- ++It would be nice if tags in each bookmarks were links pointing to the list of bookmarks with the same tag.++
- ++[[http://wikkawiki.org/BookmarkManager?show_comments=1&showall=1#comment_4505 Move markup to their own div tags]]++
- ++[[http://wikkawiki.org/BookmarkManager?show_comments=1&showall=1#comment_4507 Move helper functions, class extensions to libs/ dir]]++
- Fix PHP warnings w/r/t/ null arrays in ksort, min, max, etc.
- The codebase is getting large enough to consider distributing as a tarball/zipfile.
- Split processing of tags and links so they are independent of each other (AJAX??)
- Check/warn on link dupes during add new link operation
- Title and description: What's the diff? More useful to display URL in lieu of one of these?
- Export options: RSS, all to file, to screen (suitable for copy-and-paste), etc.
==Installation==
- Install a copy of [[http://code.google.com/p/freetag/ Freetag]] in **3rdparty/plugins/** (version 0.250 or better) //Note: Rename the installation directory (Freetag-0.250) to 'freetag'//
- Create a MySQL database (I used ##freetag## as the DB name) and use the MySQL dump below to create the necessary tables (such as ##mysql -u root freetag < wikka_freetag.sql##):
**wikka_freetag.sql**
%%(sql)
-- $Id: freetag.sql,v 1.2 2006/05/29 17:11:44 brian Exp brian $
--
-- MySQL dump 9.11
-- Freetag Structure v2.02
--
-- Table structure for table `freetags`
--
DROP TABLE IF EXISTS freetags;
CREATE TABLE freetags (
id int(10) unsigned NOT NULL auto_increment,
tag varchar(30) NOT NULL default '',
raw_tag varchar(50) NOT NULL default '',
PRIMARY KEY (id)
) TYPE=MyISAM;
--
-- Table structure for table `freetagged_objects`
--
DROP TABLE IF EXISTS freetagged_objects;
CREATE TABLE freetagged_objects (
tag_id int(10) unsigned NOT NULL default '0',
tagger_id int(10) unsigned NOT NULL default '0',
object_id int(10) unsigned NOT NULL default '0',
tagged_on datetime NOT NULL default '0000-00-00 00:00:00',
PRIMARY KEY (`tag_id`,`tagger_id`,`object_id`),
KEY `tag_id_index` (`tag_id`),
KEY `tagger_id_index` (`tagger_id`),
KEY `object_id_index` (`object_id`)
) TYPE=MyISAM;
--
-- Table structure for table `freetag_bookmarks`
--
DROP TABLE IF EXISTS freetag_bookmarks;
CREATE TABLE freetag_bookmarks (
bookmark_id int(10) unsigned NOT NULL auto_increment,
title varchar(255) NOT NULL default '',
uri varchar(255) NOT NULL,
description varchar(255) default NULL,
wikka_id varchar(75) NOT NULL,
private tinyint(1) NOT NULL default '0',
PRIMARY KEY (`bookmark_id`),
KEY `bookmark_id_index` (`bookmark_id`)
) TYPE=MyISAM;
--
-- Table structure for table `freetag_wikka_id_map`
--
DROP TABLE IF EXISTS freetag_wikka_id_map;
CREATE TABLE freetag_wikka_id_map (
tagger_id int(10) unsigned NOT NULL AUTO_INCREMENT,
wikka_id varchar(75) NOT NULL default '',
PRIMARY KEY(`tagger_id`)
) TYPE=MyISAM;
%%
- Save the following file as ##actions/bookmarks.php##:
**bookmarks.php**
%%(php)
<?php
/**
* Bookmark manager with tagging
*
* Allows users (registered or not) to tag objects such as
* bookmarks and display linked tags in the spirit of
* {@link http://de.licio.us} and {@link http://de.lirio.us}.
*
* Usage: {{bookmarks}}
*
* Credits:
* Gordon Luk's Freetag for PHP4 {@link http://www.getluky.net/freetag}
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public Licence for more details:
*
* http://www.gnu.org/copyleft/gpl.html
*
*
* @author {@link http://wikkawiki.org/BrianKoontz Brian Koontz} <brian@pongonova.net>
* @copyright Copyright (c) 2006, Brian Koontz <brian@pongonova.net>
* @name BookmarkManager
* @package Actions
* @filesource
* @license http://www.gnu.org/copyleft/gpl.html
* @since Wikka 1.1.6.1
* @version $Id: bookmarks.php,v 1.8 2006/08/10 04:03:38 brian Exp brian $
*
*/
// Check for freetag class and helper libraries
include_once('libs/Wikka.freetag.class.php');
include_once('libs/Wikka.freetag.lib.php');
/********************************************************************/
/* DB connection params */
/********************************************************************/
$freetag_options = array(
'db_user' => $this->config['mysql_user'],
'db_pass' => $this->config['mysql_password'],
'db_host' => $this->config['mysql_host'],
'db_name' => 'freetag',
'PCONNECT' => true,
);
$freetag = new wikka_freetag($freetag_options);
$wikka_id = $this->GetUserName();
$tagger_id = wikka_id_to_tagger_id($wikka_id, $this, $freetag_options);
$all_obj_count = count_objects(NULL, $this, $freetag_options);
$your_obj_count = count_objects($wikka_id, $this, $freetag_options);
$search_type = "";
if(isset($_REQUEST['action'])) {
if($_REQUEST['action']=="list_yours") {
$search_type = "search_yours";
} else {
$search_type = "search_all";
}
}
echo "<a href=\"".$this->href()."&action=list_all"."\">List all</a> (".$all_obj_count.") | ".
"<a href=\"".$this->href()."&action=list_yours"."\">List yours</a> (".$your_obj_count.") | ".
"<a href=\"".$this->href()."&action=add"."\">Add</a>".
$this->FormOpen().
"<input type=\"hidden\" name=\"action\" value=\"".$search_type."\" />".
"Search: ".
"<input name=\"search\" size=\"20\" />".
"<input type=\"radio\" name=\"search_type\" value=\"tags\" checked>Tags".
"<input type=\"radio\" name=\"search_type\" value=\"title_desc\">Title/Desc".
$this->FormClose();
if(!isset($_REQUEST['action'])) {
// Display all tags at start of session
$this->Redirect($this->href()."&action=list_all");
}
// Search by tags, titles, or descriptions
// Action name: search
// Search field name: search
// Radio buttons: search_type: "tags" or "title_desc"
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="search_yours") || ($_REQUEST['action']=="search_all")) {
if(isset($_REQUEST['search_type']) &&
($_REQUEST['search_type']=="tags") &&
isset($_REQUEST['search'])) {
if($_REQUEST['action'] == "search_yours") {
$this->Redirect($this->href()."&search_term=".$_REQUEST['search']."&action=list_yours");
} else {
$this->Redirect($this->href()."&search_term=".$_REQUEST['search']."&action=list_all");
}
}
}
// Add new entry to DB
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="add")) {
$values = array();
// Check for request from a bookmarklet...check to see if user
// is logged in...if not, redirect to login page and come back
// here when done
if(isset($_REQUEST['wikka_bookmarklet'])) {
$values['uri'] = mysql_real_escape_string($_REQUEST['uri']);
if(isset($_REQUEST['title'])) {
$values['title'] = mysql_real_escape_string($_REQUEST['title']);
}
$values['referrer'] = $_SERVER['HTTP_REFERER'];
$values['when_done'] = "add_link";
save_to_current_session($values);
//session_write_close();
if(!$this->GetUser()) {
$_SESSION['go_back'] = $this->Href(null, $this->MiniHref()."&action=add");
//session_write_close();
$this->Redirect($this->config['base_url']."UserSettings");
}
}
// The next clause checks to see if we're returning from a
// login request while trying to execute a bookmarklet
if(isset($_SESSION['when_done']) && ($_SESSION['when_done']=="add_link")) {
$values['uri'] = $_SESSION['uri'];
$values['title'] = $_SESSION['title'];
$_SESSION['when_done'] = "return";
//session_write_close();
}
if(trim($_REQUEST['tags']) != "") {
// Private bookmark?
$private = 0;
if(strpos(trim($_REQUEST['tags']), "@private") !== false)
$private = 1;

// Converts numeric tag "123" to "123_" to facilitate
// alphanumeric sorting (otherwise, PHP converts string to
// true integer).
$tags = preg_split('/\s+/', $_REQUEST['tags'], -1, PREG_SPLIT_NO_EMPTY);
$tags = preg_replace('/^([0-9]+)$/', "$1_", $tags);
$tags = implode(" ", $tags);
$this->Query("insert ".$freetag_options['db_name'].".freetag_bookmarks set ".
"title = '".mysql_real_escape_string($_REQUEST['title'])."', ".
"uri = '".mysql_real_escape_string($this->cleanUrl($_REQUEST['uri']))."', ".
"wikka_id = '".mysql_real_escape_string($wikka_id)."', ".
"private = '".$private."', ".
"description = '".mysql_real_escape_string($_REQUEST['desc'])."';");
$last_id = $this->LoadSingle("select last_insert_id();");
$freetag->tag_object($tagger_id, $last_id['last_insert_id()'], $tags);
// Check to see if it's time to return to the page from which
// the bookmarklet was called
if(isset($_SESSION['when_done']) && ($_SESSION['when_done']=="return")) {
if(isset($_SESSION['referrer'])) {
$referrer = $_SESSION['referrer'];
unset($_SESSION['go_back']);
unset($_SESSION['uri']);
unset($_SESSION['title']);
unset($_SESSION['referrer']);
unset($_SESSION['when_done']);
$this->Redirect($referrer);
}
}
// Otherwise, go back to tag/link list view
$this->Redirect($this->href()."&action=list_yours");
} else {
// Display add form
print($this->FormOpen());
?>
<input type="hidden" name="action" value="add" />
<table>
<tr>
<td align="right">Title:</td>
<td><input name="title" size="40" value="<?php echo $values['title']; ?>" /></td>
</tr>
<tr>
<td align="right">URI:</td> <td><input name="uri" size="40" value="<?php echo $values['uri']; ?>" /></td> </tr>
<tr>
<td align="right">Description:</td>
<td><input name="desc" size="40"/></td>
</tr>
<tr>
<td align="right"></td>
<td><?php echo $this->Format("Use blank space between tags, include \"@private\" for private bookmark"); ?></td>
</tr>
<tr>
<td align="right">Tags:</td>
<td><input name="tags" size="40"/></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Add" size="40" /></td>
</tr>
</table>
<?php
print($this->FormClose());
}
}
// Edit entry
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="edit") && (isset($_REQUEST['object_id']))) {
$object_id = mysql_real_escape_string($_REQUEST['object_id']);
$obj = $this->LoadSingle("select bookmark_id,title,uri,description,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$object_id.";");
// Is the logged-in user the owner?
if($wikka_id != $obj['wikka_id']) {
$this->Redirect($this->href()."&action=list_all");
}
if(isset($_REQUEST['modify']) && trim($_REQUEST['tags']) != "") {
// Delete all tags, then re-tag
$freetag->delete_all_object_tags_for_user($tagger_id,$object_id);
// Private bookmark?
$private = 0;
if(strpos(trim($_REQUEST['tags']), "@private") !== false)
$private = 1;
$this->Query("update ".$freetag_options['db_name'].".freetag_bookmarks set ".
"title = '".mysql_real_escape_string($_REQUEST['title'])."', ".
"uri = '".mysql_real_escape_string($this->cleanUrl($_REQUEST['uri']))."', ".
"wikka_id = '".mysql_real_escape_string($wikka_id)."', ".
"private = '".$private."', ".
"description = '".mysql_real_escape_string($_REQUEST['desc'])."' ".
"where bookmark_id = ".mysql_real_escape_string($object_id)." ".
"limit 1;");
$freetag->tag_object($tagger_id, $obj['bookmark_id'], $_REQUEST['tags']);
$this->Redirect($this->href()."&action=list_yours");
}
// Display add form
print($this->FormOpen());
?>
<input type="hidden" name="action" value="edit" />
<input type="hidden" name="modify" value="yes" />
<input type="hidden" name="object_id" value="<?php print $object_id ?>" />
<table>
<tr>
<td align="right">Title:</td>
<td><input name="title" size="40" value="<?php print $obj['title']; ?>"/></td>
</tr>
<tr>
<td align="right">URI:</td>
<td><input name="uri" size="40" value="<?php print $obj['uri']; ?>"/></td>
</tr>
<tr>
<td align="right">Description:</td>
<td><input name="desc" size="40" value="<?php print $obj['description']; ?>"/></td>
</tr>
<tr>
<td align="right"></td>
<td><?php echo $this->Format("Use blank space between tags, include \"@private\" for private bookmark"); ?></td>
</tr>
<tr>
<td align="right">Tags:</td>
<?php
$tags = $freetag->get_tags_on_object($object_id);
$taglist = "";
foreach($tags as $idx=>$res) {
$taglist .= trim($res['raw_tag'])." ";
}
?>
<td><input name="tags" size="40" value="<?php print $taglist?>"/></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Edit" size="40" /></td>
</tr>
</table>
<?php
print($this->FormClose());
}
// Delete entry
if(isset($_REQUEST['action']) && $_REQUEST['action']=="delete") {
$object_id = mysql_real_escape_string($_REQUEST['object_id']);
$obj = $this->LoadSingle("select bookmark_id,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".mysql_real_escape_string($object_id).";");
// Is the logged-in user the owner?
if($wikka_id != $obj['wikka_id']) {
$this->Redirect($this->href()."&action=list_all");
}
// Delete all tags first...
$freetag->delete_all_object_tags_for_user($tagger_id,$object_id);
// ...then delete object
$this->Query("delete from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".mysql_real_escape_string($object_id)." limit 1;");
$this->Redirect($this->href()."&action=list_yours");
}
// List all bookmarks (ordered by most recent)
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="list_all")) {
$offset = 0;
if(isset($_REQUEST['offset'])) {
$offset = mysql_real_escape_string($_REQUEST['offset']);
}
$limit = 25;
if(isset($_REQUEST['limit'])) {
$limit = mysql_real_escape_string($_REQUEST['limit']);
}
$tag_offset = 0;
if(isset($_REQUEST['tag_offset'])) {
$tag_offset = mysql_real_escape_string($_REQUEST['tag_offset']);
}
$tag_limit = 100;
if(isset($_REQUEST['tag_limit'])) {
$tag_limit = mysql_real_escape_string($_REQUEST['tag_limit']);
}
// Output tag cloud
$url_opts = $this->href()."&action=list_all";
$search_term = NULL;
$header = "All tags";
if(isset($_REQUEST['search_term'])) {
$search_term = mysql_real_escape_string($_REQUEST['search_term']);
$header = "All tags by search";
}
output_tag_cloud($freetag,$url_opts,NULL,$header,$tag_offset,$tag_limit,$search_term);
$ids = "";
$tag = "";
if(isset($_REQUEST['tag']) && $_REQUEST['tag'] != "") {
$ids = $freetag->get_most_recent_objects(NULL,$_REQUEST['tag'],$offset,$limit);
$tag = mysql_real_escape_string($_REQUEST['tag']);
} else {
$ids = $freetag->get_most_recent_objects(NULL,NULL,$offset,$limit);
}
foreach($ids as $key=>$val) {
$tags = $freetag->get_tags_on_object($val['object_id']);
$obj = $this->LoadSingle("select title,uri,description,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$val['object_id']." and private = 0;");
if(!isset($obj)) {
continue;
}
print "<div class=\"bookmark_block\">\n";
print "<div class=\"bookmark_title\"><a href=\"".$obj['uri']."\">".$obj['title']."</a></div>\n";
print "<div class=\"bookmark_box\"><div class=\"bookmark_desc\">".($obj['description'] ? $obj['description'] : " ")."</div>\n";
$taglist = "";
$link = $url_opts."&tag_offset=".$tag_offset."&tag_limit=".$tag_limit;
foreach($tags as $idx=>$res) {
$temp_tag = trim($res['tag']);
$taglist .= "<a href=\"".$link."&tag=".$temp_tag."\">".$temp_tag."</a> ";
}
print $taglist."by ".$obj['wikka_id']." (created: ".$val['tagged_on'].")\n";
//print "</div><div class=\"clear\"> </div><hr/>";
print "</div></div>\n";
}
// Display pagination links
$obj_count = 0;
$tag_url = "";
if($tag != "") {
$obj_count = count_tagged_objects(NULL, $tag, $this, $freetag_options, $freetag);
$tag_url = "&tag=".$tag;
} else {
$obj_count = count_objects(NULL, $this, $freetag_options);
}
$prev_offset = $offset - $limit;
$prev_url = "";
if($prev_offset < 0) {
$prev_offset = 0;
}
if($offset > 0) {
$prev_url = "<a href=\"".$this->href()."&action=list_all&offset=".$prev_offset."&limit=".$limit.$tag_url."\"><<</a>";
}
$next_offset = $offset + $limit;
$next_url = "";
if($next_offset < $obj_count - 1) {
$next_url = "<a href=\"".$this->href()."&action=list_all&offset=".$next_offset."&limit=".$limit.$tag_url."\">>></a>";
}
print "<div class=\"pagination\">";
print $prev_url."  ".$next_url;
print "</div>";
}
// List user bookmarks (ordered by most recent)
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="list_yours")) {
$offset = 0;
if(isset($_REQUEST['offset'])) {
$offset = mysql_real_escape_string($_REQUEST['offset']);
}
$limit = 25;
if(isset($_REQUEST['limit'])) {
$limit = mysql_real_escape_string($_REQUEST['limit']);
}
$tag_offset = 0;
if(isset($_REQUEST['tag_offset'])) {
$tag_offset = mysql_real_escape_string($_REQUEST['tag_offset']);
}
$tag_limit = 100;
if(isset($_REQUEST['tag_limit'])) {
$tag_limit = mysql_real_escape_string($_REQUEST['tag_limit']);
}
// Output tag cloud
$url_opts = $this->href()."&action=list_yours";
$search_term = NULL;
$header = "Your tags";
if(isset($_REQUEST['search_term'])) {
$search_term = mysql_real_escape_string($_REQUEST['search_term']);
$header = "Your tags by search";
}
output_tag_cloud($freetag,$url_opts,$tagger_id,$header,$tag_offset,$tag_limit,$search_term);
$ids = "";
$tag = "";
if(isset($_REQUEST['tag']) && $_REQUEST['tag'] != "") {
$ids = $freetag->get_most_recent_objects($tagger_id, $_REQUEST['tag'], $offset, $limit);
$tag = mysql_real_escape_string($_REQUEST['tag']);
} else {
$ids = $freetag->get_most_recent_objects($tagger_id, NULL, $offset, $limit);
}
foreach($ids as $key=>$val) {
$tags = $freetag->get_tags_on_object($val['object_id']);
$obj = $this->LoadSingle("select title,uri,description,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$val['object_id'].";");
print "<div class=\"bookmark_block\">\n";
print "<div class=\"bookmark_title\"><a href=\"".$obj['uri']."\">".$obj['title']."</a></div>\n";
print "<div class=\"bookmark_box\"><div class=\"bookmark_desc\">".($obj['description'] ? $obj['description'] : " ")."</div>\n";
print " <a href=\"".$this->href()."&action=edit&object_id=".$val['object_id']."\">edit</a>";
print "|";
print "<a href=\"".$this->href()."&action=delete&object_id=".$val['object_id']."\">delete</a><br/>";
$taglist = "";
$link = $url_opts."&tag_offset=".$tag_offset."&tag_limit=".$tag_limit;
foreach($tags as $idx=>$res) {
$normtag = trim($res['tag']);
$rawtag = trim($res['raw_tag']);
$taglist .= "<a href=\"".$link."&tag=".$normtag."\">".$rawtag."</a> ";
}
print $taglist."by ".$obj['wikka_id']." (created: ".$val['tagged_on'].")\n";
//print "<div class=\"clear\"> </div><hr/>";
print "</div></div>\n";
}
// Display pagination links
$obj_count = 0;
$tag_url = "";
if($tag != "") {
$obj_count = count_tagged_objects($wikka_id, $tag, $this, $freetag_options, $freetag);
$tag_url = "&tag=".$tag;
} else {
$obj_count = count_objects($wikka_id, $this, $freetag_options);
}
$prev_offset = $offset - $limit;
$prev_url = "";
if($prev_offset < 0) {
$prev_offset = 0;
}
if($offset > 0) {
$prev_url = "<a href=\"".$this->href()."&action=list_yours&offset=".$prev_offset."&limit=".$limit.$tag_url."\"><<</a>";
}
$next_offset = $offset + $limit;
$next_url = "";
if($next_offset < $obj_count - 1) {
$next_url = "<a href=\"".$this->href()."&action=list_yours&offset=".$next_offset."&limit=".$limit.$tag_url."\">>></a>";
}
print "<div class=\"center\">";
print $prev_url."  ".$next_url;
print "</div>";
}
?>
%%
- Save the following file as ##libs/Wikka.freetag.lib.php##:
**Wikka.freetag.lib.php**
%%(php)
<?php
/**
* Wikka.freetag.lib.php
*
* Set of helper classes for BookmarkManager. The reason these
* aren't included in the Freetag extension class
* (Wikka.freetag.class.php) is that Wikka functions defined
* outside the scope of the class aren't accessible from within
* the class, and most of these helper functions use both Freetag
* and Wikka functions.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public Licence for more details:
*
* http://www.gnu.org/copyleft/gpl.html
*
*
* @author {@link http://wikkawiki.org/BrianKoontz Brian Koontz} <brian@pongonova.net>
* @copyright Copyright (c) 2006, Brian Koontz <brian@pongonova.net>
* @name Wikka.freetag.lib.php
* @package Actions {@link http://www.wikkawiki.org/BookmarkManager}
* @filesource
* @license http://www.gnu.org/copyleft/gpl.html
* @since Wikka 1.1.6.1
* @version $Id: Wikka.freetag.lib.php,v 1.3 2006/08/10 04:03:17 brian Exp brian $
*
*/
/*********************************************************************/
/* Helper functions */
/*********************************************************************/
/**
* wikka_id_to_tagger_id()
*
* Converts Wikka id (returned from GetUserName()) to
* tagger_id. Adds Wikka id to table if not already present.
* Maps NULL -> NULL.
*
*/
function wikka_id_to_tagger_id ($wikka_id, $obj, $freetag_options) {
if(!isset($wikka_id)) {
return NULL;
}
$wikka_id = mysql_real_escape_string($wikka_id);
$res = $obj->LoadSingle("select tagger_id from ".$freetag_options['db_name'].".freetag_wikka_id_map where wikka_id = '".$wikka_id."';");
$tagger_id = $res['tagger_id'];
if(!$tagger_id) {
$obj->Query("insert ".$freetag_options['db_name'].".freetag_wikka_id_map set "."wikka_id = '".$wikka_id."';");
$res = $obj->LoadSingle("select last_insert_id();");
$tagger_id = $res['last_insert_id()'];
}
return $tagger_id;
}
/********************************************************************/
/**
* output_tag_cloud()
*
* Create a cloud of tags!
*
* NOTES:
* Set $tagger_id to NULL for all tags (except those marked
* private)
*
* If $search_term is not NULL, $tag_offset will return the offset
* of the first matching term
*/
function output_tag_cloud($freetag,$tag_page_url,$tagger_id=NULL,$header=NULL,$tag_offset=0,$tag_limit=100,$search_term=NULL) {
// Output tag cloud
$tag_count = $freetag->count_unique_tags($tagger_id,$search_term);
print "<div class='bookmark_floatr'>";
if($header) {
print $header." (".$tag_count."):<br/>";
}
print $freetag->get_tag_cloud_html_with_limits($tag_offset,$tag_limit,10,20,'px','cloud_tag',$tag_page_url,$tagger_id,$search_term);
// Display pagination links
$tag_url = "";
$prev_offset = $tag_offset - $tag_limit;
$prev_url = "";
if($prev_offset < 0) {
$prev_offset = 0;
}
$search_tag = "";
if(isset($search_term)) {
$search_tag = "&search_term=".$search_term;
}
if($tag_offset > 0) {
$prev_url = "<a href=\"".$tag_page_url."&tag_offset=".$prev_offset."&tag_limit=".$tag_limit.$search_tag."\"><<</a>";
}
$next_offset = $tag_offset + $tag_limit;
$next_url = "";
if($next_offset < $tag_count - 1) {
$next_url = "<a href=\"".$tag_page_url."&tag_offset=".$next_offset."&tag_limit=".$tag_limit.$search_tag.$tag_url."\">>></a>";
}
print "<div class=\"pagination\">";
print $prev_url."  ".$next_url;
print "</div>";
print "</div>";
// print "<div class=\"clear\"> </div>";
}
/********************************************************************/
/**
* count_objects()
*
* Returns a count of objects (bookmarks, links, etc.)
*
* NOTE: Set $wikka_id to NULL for all objects (except those marked
* private)
*/
function count_objects($wikka_id, $obj, $freetag_options) {
if(isset($wikka_id)) {
$wikka_sql = "AND wikka_id = '".mysql_real_escape_string($wikka_id)."'";
} else {
$wikka_sql = "AND private = 0";
}
$res = $obj->LoadSingle("select COUNT(*) as count from ".$freetag_options['db_name'].".freetag_bookmarks where 1 ".$wikka_sql.";");
$count = $res['count'];
return $count;
}
/********************************************************************/
/**
* count_tagged_objects()
*
* Returns a count of tagged objects (bookmarks, links, etc.)
*
* NOTE: Set $wikka_id to NULL for all taggedobjects regardless of
* owner (except those marked private)
*/
function count_tagged_objects($wikka_id, $tag, $obj, $freetag_options, $freetag) {
$num_objs = count_objects($wikka_id, $obj, $freetag_options);
$tagger_id = wikka_id_to_tagger_id($wikka_id, $obj, $freetag_options);
$ids = $freetag->get_most_recent_objects($tagger_id, $tag, 0, $num_objs);
return count($ids);
}
/********************************************************************/
/**
* save_to_current_session()
*
* Save various fields to the current session
*
* Input: Associative array of session tags->values
*
* Return: Name of session
*
*/
function save_to_current_session($values) {
foreach($values as $tag=>$value) {
$_SESSION[$tag] = $value;
}
return $_SESSION['name'];
}
/********************************************************************/
?>
%%
- Save the following file as ##libs/Wikka.freetag.class.php##:
**Wikka.freetag.class.php**
%%(php)
<?php
/*******************************************************************
* $Id: Wikka.freetag.class.php,v 1.8 2006/08/10 04:03:28 brian Exp brian $
*
* Wikka.freetag.class.php
*
* Extension to Gordon Luk's Freetag for PHP4 (see credits) for
* use with Wikka Bookmark action/handler
*
* Credits:
* Gordon Luk's Freetag for PHP4 (http://www.getluky.net/freetag)
*
* Author:
* Copyright (c) 2006 Brian Koontz <brian@pongonova.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public Licence for more details:
*
* http://www.gnu.org/copyleft/gpl.html
*
*********************************************************************/
// Check for freetag library
$freetag_lib = '3rdparty/plugins/freetag/freetag.class.php';
if(!is_file($freetag_lib)) {
print("<br/><br/><div class=\"error\">".$this->Format("Can't find $freetag_lib!")."<br/><br/>\n");
die();
}
include_once($freetag_lib);
/********************************************************************/
/* Freetag subclass (defines additional support methods) */
/********************************************************************/
class wikka_freetag extends freetag {
function wikka_freetag($freetag_options) {
parent::freetag($freetag_options);
$this->_normalized_valid_chars = '@_a-zA-Z0-9';
}

/*******************************************************************
* get_tag_cloud_html_with_limits
*
* Extension to get_tag_cloud_html() that permits specifying
* an offset and limit (useful for presenting more tags than
* can be displayed at one time).
*
* See get_tag_cloud_html() comments for description of this
* function.
*
* @param int Specify starting record (default: 0). Will be set to
* offset of first matching term if $search_term is not NULL
* @param int The maximum number of tags to return. (default: 100)
* @param int The minimum font size in the cloud. (default: 10)
* @param int The maximum number of tags to return. (default: 20)
* @param string The "units" for the font size (i.e. 'px', 'pt',
* 'em') (default: px)
* @param string The class to use for all spans in the cloud.
* (default: cloud_tag)
* @param string The tag page URL (default: /tag/)
* @param int The tagger ID (default: NULL)
* @param string Optional search term (default: NULL)
*
* @return string Returns an HTML snippet that can be used
* directly as a tag cloud.
*/
function get_tag_cloud_html_with_limits($offset = 0, $limit = 100, $min_font_size = 10, $max_font_size = 20, $font_units = 'px', $span_class = 'cloud_tag', $tag_page_url = '/tag/', $tagger_id = NULL, $search_term = NULL) {
$tag_list = $this->get_tag_cloud_tags_with_limits($offset, $limit, $tagger_id,$search_term);
// Get the maximum qty of tagged objects in the set
$max_qty = max(array_values($tag_list));
// Get the min qty of tagged objects in the set
$min_qty = min(array_values($tag_list));
// For ever additional tagged object from min to max, we add
// $step to the font size.
$spread = $max_qty - $min_qty;
if (0 == $spread) { // Divide by zero
$spread = 1;
}
$step = ($max_font_size - $min_font_size)/($spread);
// Since the original tag_list is alphabetically ordered,
// we can now create the tag cloud by just putting a span
// on each element, multiplying the diff between min and qty
// by $step.
$cloud_html = '';
$cloud_spans = array();
foreach ($tag_list as $tag => $qty) {
//if(strpos($tag, "private") !== false) continue;
$size = $min_font_size + ($qty - $min_qty) * $step;
$search_tag = "";
if(isset($search_term)) {
$search_tag = "&search_term=".$search_term;
}
$cloud_span[] = '<span class="' . $span_class . '" style="font-size: '. $size . $font_units . '"><a href="'.$tag_page_url . "&tag_offset=".$offset."&tag_limit=".$limit."&tag=".$tag.$search_tag. '">' . htmlspecialchars(stripslashes($tag)) . '</a></span>';
}
// Remove <br/> from the following line to get a "tag
// cloud" rather than a "tag list"
$cloud_html = join("<br/>\n ", $cloud_span);
return $cloud_html;
}
/*
* get_tag_cloud_tags_with_limits
*
* Extension to get_tag_cloud_tags() that permits specifying
* an offset and limit (useful for presenting more tags than
* can be displayed at one time). Also, tags are returned
* in true alphanumeric sequence across the entire record set,
* rather than across the most popular tags.
*
* See get_tag_cloud_tags() comments for description of this
* function.
* @param int Specify starting record (default: 0). Will be set to
* offset of first matching tag if $search_term is not NULL.
* @param int The maximum number of tags to return (default: 100)
* @param int Tag owner (default: NULL)
* @param string Optional search term (default: NULL)
*
* @return array Returns an array where the keys are normalized
* tags, and the
* values are numeric quantity of objects tagged with that tag.
*/
function get_tag_cloud_tags_with_limits($offset = 0, $limit = 100, $tagger_id = NULL, $search_term = NULL) {
$db = $this->db;
$limit_sql = "LIMIT ".$offset.", ".$limit;
if(isset($tagger_id) && ($tagger_id > 0)) {
$tagger_sql = "AND tagger_id = $tagger_id";
} else {
$tagger_sql = "";
}
$search_sql = "";
if(isset($search_term)) {
$search_sql = "AND tag LIKE \"".$search_term."%\"";
//$limit_sql = "LIMIT ".$limit;
//$offset = 0;
}
$prefix = $this->_table_prefix;
$sql = "SELECT tag, COUNT(object_id) AS quantity
FROM ${prefix}freetags INNER JOIN
${prefix}freetagged_objects
ON (${prefix}freetags.id = tag_id)
WHERE 1
$tagger_sql
$search_sql
GROUP BY tag
ORDER BY tag ASC
$limit_sql
";
$rs = $db->Execute($sql) or die("Syntax Error: $sql");
while(!$rs->EOF) {
$retarr[$rs->fields['tag']] = $rs->fields['quantity'];
$rs->MoveNext();
if($search_term) {
//$offset++;
}
}
ksort($retarr);
return $retarr;
}
/**
* count_unique_tags
* Returns the total number of unique tags in the system.
*
* @param int The unique ID of the person to restrict results to.
* @param string Optional search string (default: NULL)
*
* @return int Returns the count
*/
function count_unique_tags($tagger_id = NULL, $search_term = NULL) {
$db = $this->db;
if(isset($tagger_id) && ($tagger_id > 0)) {
$tagger_sql = "AND tagger_id = $tagger_id";
} else {
$tagger_sql = "";
}
$search_sql = "";
if(isset($search_term)) {
$search_sql = "AND tag LIKE \"".$search_term."%\"";
//$limit_sql = "LIMIT ".$limit;
//$offset = 0;
}
$prefix = $this->_table_prefix;
$sql = "SELECT DISTINCT tag, tag_id, COUNT(*) as count
FROM ${prefix}freetags INNER JOIN ${prefix}freetagged_objects ON (id = tag_id)
$tagger_sql
$search_sql
GROUP BY NULL
";
$rs = $db->Execute($sql) or die("Syntax Error: $sql");
if(!$rs->EOF) {
return $rs->fields['count'];
}
return false;
}
}
/********************************************************************/
?>
%%
- Adjust the DB connection parameters for your own installation (only necessary if you deviate from the DB instructions above)

- Create the CSS stylesheet for the action, ##css/bookmarkmgr.css##:
%%(php)
.bookmark_floatr {
float: right;
text-align: left;
width: 10%;
margin-left: 15px;
padding: 4px;
background: #EEE;
border: 1px solid #CCC;
line-height: 95%;
}
.bookmark_box {
width: 85%;
font-size: 85%;
padding: 0px 5px;
color: #666;
background: #EEE;
border: 1px solid #CCC;
}
.bookmark_block {
width: 85%;
border-bottom: 3px solid #DDD;
padding: 8px;
}
.bookmark_desc {
font-size: 100%;
font-weight: bold;
margin: 0px;
padding: 0px;
}
.bookmark_title {
font-size: 150%;
font-weight: bold;
padding: 5px 0px;
}
.pagination {
text-align: center;
margin: 20px auto;
}
%%
- In order to use the stylesheet, add the following lines to the files indicated (click [[http://wush.net/trac/wikka/ticket/246 here]] for a justification as to why these changes are necessary to the core):
**wikka.config.php:**
%%(php)
'additional_stylesheets' => 'bookmarkmgr.css',
%%
**actions/header.php:**
%%(php)
// Locate this line...
<link rel="stylesheet" type="text/css" href="css/<?php echo $this->GetConfigValue("stylesheet") ?>" />
// ...and add the following codeblock:
<?php
if($this->GetConfigValue("additional_stylesheets") != NULL) {
$sheets = preg_split("/[\s]+/", $this->GetConfigValue("additional_stylesheets"));
foreach($sheets as $sheet) {
echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"css/".$sheet."\" />\n";
}
}
?>
%%
- Add the ""{{bookmarks}}"" action code to a new page

==Bookmarklet support==
BookmarkManager now supports "bookmarklets," which is a small snippet of Javascript saved as a browser bookmark that allows you to bookmark a webpage "on the fly." The following patch must be applied to ##actions/usersettings.php## if you want to restrict bookmarklets to registered users (recommended):
%%(sh)
===================================================================
RCS file: RCS/usersettings.php,v
retrieving revision 1.1
diff -u -r1.1 usersettings.php
--- usersettings.php 2006/06/18 06:03:03 1.1
+++ usersettings.php 2006/06/24 06:40:13
@@ -1,5 +1,7 @@
<?php
/**
+ * $Id: usersettings.php,v 1.5 2006/06/24 06:40:07 brian Exp brian $
+ *
* Display a form to register, login and change user settings.
*
* @package Actions
@@ -334,6 +336,13 @@
// is user trying to log in or register?
if (isset($_POST['action']) && ($_POST['action'] == 'login'))
{
+ // Login request was redirected from elsewhere...let's make
+ // sure to go back if requested by setting $referrer
+ $referrer = null;
+ if(isset($_SESSION['go_back'])) {
+ $referrer = $_SESSION['go_back'];
+ }
+
// if user name already exists, check password
if (isset($_POST['name']) && $existingUser = $this->LoadUser($_POST['name']))
{
@@ -349,7 +358,11 @@
break;
default:
$this->SetUser($existingUser);
- $this->Redirect($this->href());
+ if($referrer == null)
+ $this->Redirect($this->href());
+ else {
+ $this->Redirect($referrer);
+ }
}
}
else // otherwise, proceed to registration
@@ -417,7 +430,11 @@

// log in
$this->SetUser($this->LoadUser($name));
- $this->Redirect($this->href('', '', 'registered=true'));
+ if($referrer == null)
+ $this->Redirect($this->href('', '', 'registered=true'));
+ else {
+ $this->Redirect($referrer);
+ }
}
}
}
@@ -542,4 +559,4 @@
<?php
print($this->FormClose());
}
%%
Save the following as a bookmark in your browser:
%%
javascript:location.href='http://your.site.com/wiki/wikka.php?wakka=BookmarkPage&action=add&uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&wikka_bookmarklet=1'
(Don't forget to change "your.site.com" as appropriate for your installation!)
%%
TODO: Implement as a popup:
%%
post bookmarklet (with popup): javascript:void(open('http://de.lirio.us/rubric/post?uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&when_done=close','Rubric','toolbar=no,width=700,height=325,scrollbars'));
%%
==Other stuff==
- [[BookmarkManagerImportScripts Import scripts]]

==Category==
CategoryDevelopmentDiscussion
CategoryDevelopmentActions
CategoryUserContributions


Revision [16395]

Edited on 2007-04-10 10:05:19 by BrianKoontz [Removed call-by-ref default args due to PHP flakiness]
Additions:
function output_tag_cloud($freetag,$tag_page_url,$tagger_id=NULL,$header=NULL,$tag_offset=0,$tag_limit=100,$search_term=NULL) {
function get_tag_cloud_html_with_limits($offset = 0, $limit = 100, $min_font_size = 10, $max_font_size = 20, $font_units = 'px', $span_class = 'cloud_tag', $tag_page_url = '/tag/', $tagger_id = NULL, $search_term = NULL) {
function get_tag_cloud_tags_with_limits($offset = 0, $limit = 100, $tagger_id = NULL, $search_term = NULL) {
Deletions:
function output_tag_cloud($freetag,$tag_page_url,$tagger_id=NULL,$header=NULL,&$tag_offset=0,$tag_limit=100,$search_term=NULL) {
function get_tag_cloud_html_with_limits(&$offset = 0, $limit = 100, $min_font_size = 10, $max_font_size = 20, $font_units = 'px', $span_class = 'cloud_tag', $tag_page_url = '/tag/', $tagger_id = NULL, $search_term = NULL) {
function get_tag_cloud_tags_with_limits(&$offset = 0, $limit = 100, $tagger_id = NULL, $search_term = NULL) {


Revision [16391]

Edited on 2007-04-09 14:01:12 by BrianKoontz [Modified link]
Additions:
- Install a copy of [[http://code.google.com/p/freetag/ Freetag]] in **3rdparty/plugins/** (version 0.250 or better) //Note: Rename the installation directory (Freetag-0.250) to 'freetag'//
Deletions:
- Install a copy of [[http://www.getluky.net/freetag/ Freetag]] in **3rdparty/plugins/** (version 0.250 or better) //Note: Rename the installation directory (Freetag-0.250) to 'freetag'//


Revision [15628]

Edited on 2006-11-09 20:07:15 by BrianKoontz [Minor edit]
Additions:
An integrated bookmark manager/[[http://en.wikipedia.org/wiki/Social_bookmarking social bookmarking]] framework in the spirit of [[de.lirio.us]]. //Note:// Scroll down to the import section for scripts to import de.lirio.us data and other bookmarks into the BookmarkManager.
Deletions:
An integrated bookmark manager in the spirit of [[de.lirio.us]]. //Note:// Scroll down to the import section for scripts to import de.lirio.us data and other bookmarks into the BookmarkManager.


Revision [15128]

Edited on 2006-08-13 20:50:13 by BrianKoontz [Added TODOs]
Additions:
- Export options: RSS, all to file, to screen (suitable for copy-and-paste), etc.


Revision [15127]

Edited on 2006-08-13 20:42:38 by BrianKoontz [Added TODOs]
Additions:
- Check/warn on link dupes during add new link operation
- Title and description: What's the diff? More useful to display URL in lieu of one of these?


Revision [15104]

Edited on 2006-08-10 11:09:12 by BrianKoontz [Added TODOs]
Additions:
- Search functionality (multi-tag, by title/desc)
- Split processing of tags and links so they are independent of each other (AJAX??)
Deletions:
- Search functionality


Revision [15101]

Edited on 2006-08-10 00:18:52 by BrianKoontz [Added tag search functionality]
Additions:
- Progress is going well. Add/edit/delete capabilities have been added. Two Perl scripts (see below) have been provided that allow you to export your bookmarks from de.lirio.us and import them into BookmarkManager. Beta currently running under Wikka 1.1.6.2-alpha. A page of 25 links (out of 600+) and 100 tags (out of 1200+) renders in approximately 1.1 seconds. A tag seach of 1207 tags takes approximately 1.2 seconds.
Deletions:
- Progress is going well. Add/edit/delete capabilities have been added. Two Perl scripts (see below) have been provided that allow you to export your bookmarks from de.lirio.us and import them into BookmarkManager. Beta currently running under Wikka 1.1.6.2-alpha. A page of 25 links (out of 200+) and about 100 tags renders in approximately 1.1 seconds.


Revision [15100]

Edited on 2006-08-10 00:16:18 by BrianKoontz [Added tag search functionality]
Additions:
- Tags are now searchable! Searchable titles/descriptions to come...
- The codebase is getting large enough to consider distributing as a tarball/zipfile.
* @version $Id: bookmarks.php,v 1.8 2006/08/10 04:03:38 brian Exp brian $
$search_type = "";
if(isset($_REQUEST['action'])) {
if($_REQUEST['action']=="list_yours") {
$search_type = "search_yours";
$search_type = "search_all";
"<a href=\"".$this->href()."&action=add"."\">Add</a>".
$this->FormOpen().
"<input type=\"hidden\" name=\"action\" value=\"".$search_type."\" />".
"Search: ".
"<input name=\"search\" size=\"20\" />".
"<input type=\"radio\" name=\"search_type\" value=\"tags\" checked>Tags".
"<input type=\"radio\" name=\"search_type\" value=\"title_desc\">Title/Desc".
$this->FormClose();
// Search by tags, titles, or descriptions
// Action name: search
// Search field name: search
// Radio buttons: search_type: "tags" or "title_desc"
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="search_yours") || ($_REQUEST['action']=="search_all")) {
if(isset($_REQUEST['search_type']) &&
($_REQUEST['search_type']=="tags") &&
isset($_REQUEST['search'])) {
if($_REQUEST['action'] == "search_yours") {
$this->Redirect($this->href()."&search_term=".$_REQUEST['search']."&action=list_yours");
} else {
$this->Redirect($this->href()."&search_term=".$_REQUEST['search']."&action=list_all");
}
$values['uri'] = mysql_real_escape_string($_REQUEST['uri']);
$values['title'] = mysql_real_escape_string($_REQUEST['title']);
$object_id = mysql_real_escape_string($_REQUEST['object_id']);
$object_id = mysql_real_escape_string($_REQUEST['object_id']);
$offset = mysql_real_escape_string($_REQUEST['offset']);
$limit = mysql_real_escape_string($_REQUEST['limit']);
$tag_offset = mysql_real_escape_string($_REQUEST['tag_offset']);
$tag_limit = mysql_real_escape_string($_REQUEST['tag_limit']);
$search_term = NULL;
$header = "All tags";
if(isset($_REQUEST['search_term'])) {
$search_term = mysql_real_escape_string($_REQUEST['search_term']);
$header = "All tags by search";
output_tag_cloud($freetag,$url_opts,NULL,$header,$tag_offset,$tag_limit,$search_term);
$tag = mysql_real_escape_string($_REQUEST['tag']);
$offset = mysql_real_escape_string($_REQUEST['offset']);
$limit = mysql_real_escape_string($_REQUEST['limit']);
$tag_offset = mysql_real_escape_string($_REQUEST['tag_offset']);
$tag_limit = mysql_real_escape_string($_REQUEST['tag_limit']);
$search_term = NULL;
$header = "Your tags";
if(isset($_REQUEST['search_term'])) {
$search_term = mysql_real_escape_string($_REQUEST['search_term']);
$header = "Your tags by search";
output_tag_cloud($freetag,$url_opts,$tagger_id,$header,$tag_offset,$tag_limit,$search_term);
$tag = mysql_real_escape_string($_REQUEST['tag']);
* @version $Id: Wikka.freetag.lib.php,v 1.3 2006/08/10 04:03:17 brian Exp brian $
* NOTES:
* Set $tagger_id to NULL for all tags (except those marked
* private)
* If $search_term is not NULL, $tag_offset will return the offset
* of the first matching term
function output_tag_cloud($freetag,$tag_page_url,$tagger_id=NULL,$header=NULL,&$tag_offset=0,$tag_limit=100,$search_term=NULL) {
$tag_count = $freetag->count_unique_tags($tagger_id,$search_term);
print $freetag->get_tag_cloud_html_with_limits($tag_offset,$tag_limit,10,20,'px','cloud_tag',$tag_page_url,$tagger_id,$search_term);
$search_tag = "";
if(isset($search_term)) {
$search_tag = "&search_term=".$search_term;
$prev_url = "<a href=\"".$tag_page_url."&tag_offset=".$prev_offset."&tag_limit=".$tag_limit.$search_tag."\"><<</a>";
$next_url = "<a href=\"".$tag_page_url."&tag_offset=".$next_offset."&tag_limit=".$tag_limit.$search_tag.$tag_url."\">>></a>";
* $Id: Wikka.freetag.class.php,v 1.8 2006/08/10 04:03:28 brian Exp brian $
* @param int Specify starting record (default: 0). Will be set to
* offset of first matching term if $search_term is not NULL
* @param int The tagger ID (default: NULL)
* @param string Optional search term (default: NULL)
function get_tag_cloud_html_with_limits(&$offset = 0, $limit = 100, $min_font_size = 10, $max_font_size = 20, $font_units = 'px', $span_class = 'cloud_tag', $tag_page_url = '/tag/', $tagger_id = NULL, $search_term = NULL) {
$tag_list = $this->get_tag_cloud_tags_with_limits($offset, $limit, $tagger_id,$search_term);
$search_tag = "";
if(isset($search_term)) {
$search_tag = "&search_term=".$search_term;
$cloud_span[] = '<span class="' . $span_class . '" style="font-size: '. $size . $font_units . '"><a href="'.$tag_page_url . "&tag_offset=".$offset."&tag_limit=".$limit."&tag=".$tag.$search_tag. '">' . htmlspecialchars(stripslashes($tag)) . '</a></span>';
* @param int Specify starting record (default: 0). Will be set to
* offset of first matching tag if $search_term is not NULL.
* @param string Optional search term (default: NULL)
function get_tag_cloud_tags_with_limits(&$offset = 0, $limit = 100, $tagger_id = NULL, $search_term = NULL) {
$limit_sql = "LIMIT ".$offset.", ".$limit;
$search_sql = "";
if(isset($search_term)) {
$search_sql = "AND tag LIKE \"".$search_term."%\"";
//$limit_sql = "LIMIT ".$limit;
//$offset = 0;
}
$search_sql
$limit_sql
if($search_term) {
//$offset++;
* @param string Optional search string (default: NULL)
function count_unique_tags($tagger_id = NULL, $search_term = NULL) {
$search_sql = "";
if(isset($search_term)) {
$search_sql = "AND tag LIKE \"".$search_term."%\"";
//$limit_sql = "LIMIT ".$limit;
//$offset = 0;
}
$sql = "SELECT DISTINCT tag, tag_id, COUNT(*) as count
$search_sql
Deletions:
* @version $Id: bookmarks.php,v 1.7 2006/08/08 19:48:31 brian Exp brian $
"<a href=\"".$this->href()."&action=add"."\">Add</a><br/>";
$values['uri'] = $_REQUEST['uri'];
$values['title'] = $_REQUEST['title'];
$object_id = $_REQUEST['object_id'];
$object_id = $_REQUEST['object_id'];
$offset = $_REQUEST['offset'];
$limit = $_REQUEST['limit'];
$tag_offset = $_REQUEST['tag_offset'];
$tag_limit = $_REQUEST['tag_limit'];
output_tag_cloud($freetag,$url_opts,NULL,"All tags",$tag_offset,$tag_limit);
$tag = $_REQUEST['tag'];
$offset = $_REQUEST['offset'];
$limit = $_REQUEST['limit'];
$tag_offset = $_REQUEST['tag_offset'];
$tag_limit = $_REQUEST['tag_limit'];
output_tag_cloud($freetag,$url_opts,$tagger_id,"Your tags",$tag_offset,$tag_limit);
$tag = $_REQUEST['tag'];
* @version $Id: Wikka.freetag.lib.php,v 1.2 2006/08/08 21:48:11 brian Exp brian $
* NOTE: Set $tagger_id to NULL for all tags (except those marked
function output_tag_cloud($freetag,$tag_page_url,$tagger_id=NULL,$header=NULL,$tag_offset=0,$tag_limit=100) {
$tag_count = $freetag->count_unique_tags($tagger_id);
print $freetag->get_tag_cloud_html_with_limits($tag_offset,$tag_limit,10,20,'px','cloud_tag',$tag_page_url."&tag_offset=".$tag_offset."&tag_limit=".$tag_limit."&tag=",$tagger_id);
$prev_url = "<a href=\"".$tag_page_url."&tag_offset=".$prev_offset."&tag_limit=".$tag_limit."\"><<</a>";
$next_url = "<a href=\"".$tag_page_url."&tag_offset=".$next_offset."&tag_limit=".$tag_limit.$tag_url."\">>></a>";
* $Id: Wikka.freetag.class.php,v 1.7 2006/08/08 21:48:11 brian Exp brian $
* @param int Specify starting record (default: 0)
function get_tag_cloud_html_with_limits($offset = 0, $limit = 100, $min_font_size = 10,
$max_font_size = 20, $font_units = 'px', $span_class = 'cloud_tag',
$tag_page_url = '/tag/', $tagger_id = NULL) {
$tag_list = $this->get_tag_cloud_tags_with_limits($offset, $limit, $tagger_id);
$cloud_span[] = '<span class="' . $span_class . '"
style="font-size: '. $size . $font_units . '"><a href="'.$tag_page_url
. $tag . '">' . htmlspecialchars(stripslashes($tag)) . '</a></span>';
* @param int Specify starting record (default: 0)
function get_tag_cloud_tags_with_limits($offset = 0, $limit = 100, $tagger_id = NULL) {
LIMIT $offset, $limit
function count_unique_tags($tagger_id = NULL) {
$sql = "SELECT DISTINCT tag_id, COUNT(*) as count


Revision [15097]

Edited on 2006-08-08 18:18:25 by BrianKoontz [Added screenshots]
Additions:
- Install a copy of [[http://www.getluky.net/freetag/ Freetag]] in **3rdparty/plugins/** (version 0.250 or better) //Note: Rename the installation directory (Freetag-0.250) to 'freetag'//
Deletions:
- Install a copy of [[http://www.getluky.net/freetag/ Freetag]] in **3rdparty/plugins/** (version 0.250 or better)


Revision [15096]

Edited on 2006-08-08 18:16:33 by BrianKoontz [Added screenshots]
Additions:
An integrated bookmark manager in the spirit of [[de.lirio.us]]. //Note:// Scroll down to the import section for scripts to import de.lirio.us data and other bookmarks into the BookmarkManager.
Deletions:
An integrated bookmark manager in the spirit of [[de.lirio.us]]. //Note:// de.lirio.us will be shutting down within the month (end of July 2006), so if you plan on exporting your de.lirio.us bookmarks, better do it soon!


Revision [15095]

Edited on 2006-08-08 18:14:31 by BrianKoontz [Added screenshots]
Additions:
::c::
::c::


Revision [15094]

Edited on 2006-08-08 18:08:32 by BrianKoontz [Added screenshots]
Additions:
==Screenshots==
**Main page**
{{image class="left" alt="BookmarkManager main page" url="http://durango.dcccd.edu/images/ss1.jpg"}}
**Add bookmark page**
{{image class="left" alt="BookmarkManager add page" url="http://durango.dcccd.edu/images/ss2.jpg"}}


Revision [15093]

Edited on 2006-08-08 17:54:37 by BrianKoontz [Minor mods/edits; refactor; layout change]
Additions:
- More cool stuff: Support for bookmarklets (small Javascript bookmark that immediately saves the page you are viewing and returns to the page), tag links within link descriptors, better use of screen real estate using CSS.
* @version $Id: Wikka.freetag.lib.php,v 1.2 2006/08/08 21:48:11 brian Exp brian $
print "<div class='bookmark_floatr'>";
// print "<div class=\"clear\"> </div>";
* $Id: Wikka.freetag.class.php,v 1.7 2006/08/08 21:48:11 brian Exp brian $
// Remove <br/> from the following line to get a "tag
// cloud" rather than a "tag list"
$cloud_html = join("<br/>\n ", $cloud_span);
.bookmark_floatr {
float: right;
text-align: left;
width: 10%;
margin-left: 15px;
padding: 4px;
border: 1px solid #CCC;
line-height: 95%;
width: 85%;
width: 85%;
Deletions:
- More cool stuff: Support for bookmarklets (small Javascript bookmark that immediately saves the page you are viewing and returns to the page), tag links within link descriptors.
* @version $Id: Wikka.freetag.lib.php,v 1.1 2006/08/08 19:40:50 brian Exp brian $
print "<div class='floatr'>";
print "<div class=\"clear\"> </div>";
* $Id: Wikka.freetag.class.php,v 1.6 2006/06/05 03:32:33 brian Exp brian $
$cloud_html = join("\n ", $cloud_span);


Revision [15092]

Edited on 2006-08-08 17:02:45 by BrianKoontz [Minor mods/edits; refactor]
Additions:
- Save the following file as ##libs/Wikka.freetag.lib.php##:
**Wikka.freetag.lib.php**
Deletions:
- Save the following file as ##libs/Wikka.fretag.lib.php##:


Revision [15091]

Edited on 2006-08-08 16:01:08 by BrianKoontz [Minor mods; refactor]
Additions:
- Save the following file as ##libs/Wikka.fretag.lib.php##:
Deletions:
- Save teh following file as ##libs/Wikka.fretag.lib.php##:


Revision [15090]

Edited on 2006-08-08 15:59:56 by BrianKoontz [Minor mods; refactor]
Additions:
- ++$freetag_options -> why not use the params provided by wikka ($this->mysql_host, etc.)?++
- ++[[http://wikkawiki.org/BookmarkManager?show_comments=1&showall=1#comment_4507 Move helper functions, class extensions to libs/ dir]]++
* @version $Id: bookmarks.php,v 1.7 2006/08/08 19:48:31 brian Exp brian $
// Check for freetag class and helper libraries
include_once('libs/Wikka.freetag.lib.php');
'db_user' => $this->config['mysql_user'],
'db_pass' => $this->config['mysql_password'],
'db_host' => $this->config['mysql_host'],
- Save teh following file as ##libs/Wikka.fretag.lib.php##:
* Wikka.freetag.lib.php
* Set of helper classes for BookmarkManager. The reason these
* aren't included in the Freetag extension class
* (Wikka.freetag.class.php) is that Wikka functions defined
* outside the scope of the class aren't accessible from within
* the class, and most of these helper functions use both Freetag
* and Wikka functions.
* @name Wikka.freetag.lib.php
* @package Actions {@link http://www.wikkawiki.org/BookmarkManager}
* @version $Id: Wikka.freetag.lib.php,v 1.1 2006/08/08 19:40:50 brian Exp brian $
- Adjust the DB connection parameters for your own installation (only necessary if you deviate from the DB instructions above)
Deletions:
- $freetag_options -> why not use the params provided by wikka ($this->mysql_host, etc.)?
- [[http://wikkawiki.org/BookmarkManager?show_comments=1&showall=1#comment_4507 Move helper functions, class extensions to libs/ dir]]
* @version $Id: bookmarks.php,v 1.5 2006/07/05 22:52:20 brian Exp brian $
'db_user' => '',
'db_pass' => '',
'db_host' => 'localhost',
echo "<h1>BookmarkTest</h1>";
echo $_SESSION['when_done'];
<?php echo $_SESSION['when_done']; ?>
<?php echo $_SESSION['referrer']; ?>
- Adjust the DB connection parameters for your own installation


Revision [15089]

Edited on 2006-08-08 15:54:50 by BrianKoontz [Refactor]
Additions:
- [[BookmarkManagerImportScripts Import scripts]]


Revision [15087]

Edited on 2006-08-08 15:51:56 by BrianKoontz [Refactor]
Deletions:
- The two Perl scripts that follow can be used to import bookmarks from de.lirio.us. Use your browser to save a page of bookmarks as an HTML file (you might have to save multiple pages; that's OK, the script can handle it). Export the HTML data into text format:
parseDelirious.pl file1.html file2.html file3.html > myLinks.txt
Change the ##$base_url##, ##$wikiname##, and ##$password## global vars in ##importDelirious.pl##, then run against the file created in the previous step:
importDelirious.pl myLinks.txt
##parseDelirious.pl##
%%(perl)
#! /usr/bin/perl
#
# $Id: parseDelirious.pl,v 1.2 2006/05/30 03:27:21 brian Exp brian $
#
# parseDelirious.pl - Parses a de.lirio.us screen dump (as saved by
# Firefox)
#
#####################################################################
require HTML::TreeBuilder;
foreach my $filename(@ARGV) {
my $tree = HTML::TreeBuilder->new;
$tree->parse_file($filename);
$tree->elementify();
@nodes = $tree->look_down("class","xfolkentry");
foreach $node(@nodes) {
$_ = $node->look_down("class","uri");
$link = $_->extract_links();
$title = $link->[0]->[1]->as_text();
print "Title: $title\n";
print "URI: $link->[0]->[0]\n";
# Get description, if any
$_=$node->look_down("class","extended");
print "Desc: ";
if($_) {
$desc = $_->as_text();
print "$desc";
print "\n";
# A de.lirio.us export quirk prevents some tags from
# displaying; default these to "@private" for later review
@_ = $node->look_down("class","tag");
print "Tags: ";
if($#_ < 0) {
print "\@private";
foreach $tagnode(@_) {
$link = $tagnode->extract_links();
$tag = $link->[0]->[1]->as_text();
print "$tag ";
}
print "\n\n\n";
$tree = $tree->delete;
##importDelirious.pl##
%%(perl)
#! /usr/bin/perl
#
# $Id: importDelirious.pl,v 1.3 2006/05/31 04:43:19 brian Exp brian $
#
# importDelirious.pl -- Imports file created by parseDelirious.pl
#
# Usage: importDelirious.pl exportFile
#
#####################################################################
require LWP::UserAgent;
require HTTP::Cookies;
###Global###
$base_url = "http://some.url.com/wiki/";
$bookmark_page = "Bookmarks";
$wikiname = "YourName";
$password = "yourpassword";
$ua = LWP::UserAgent->new;
$cookie_jar = HTTP::Cookies->new(file => "lwpcookies.txt",
autosave => 1);
$ua->cookie_jar($cookie_jar);
# Login
$login_url = $base_url."wikka.php?wakka=UserSettings";
my $req = HTTP::Request->new(POST=>"$login_url");
$req->content_type('application/x-www-form-urlencoded');
$req->content("name=$wikiname&password=$password&action=login&wakka=UserSettings");
my $res = $ua->request($req);
$cookie_jar->extract_cookies($res);
# Import
open(IN, "<$ARGV[0]") || die "Can't open $ARGV[0] for reading!";
# Set autoflush so progress is displayed
my $oldfh = select(STDOUT); $| = 1; select($oldfh);
print "Importing...";
while(<IN>) {
print ".";
next until /Title: /;
chomp;
$title = (split(": ",$_))[1];
$_ = <IN>;
chomp;
$uri = (split(": ",$_))[1];
$_ = <IN>;
chomp;
$desc = (split(": ",$_))[1];
$_ = <IN>;
chomp;
$tags = (split(": ",$_))[1];
$add_url = $base_url."wikka.php?wakka=".$bookmark_page."&action=add";
$req = HTTP::Request->new(POST=>"$add_url");
$req->content_type('application/x-www-form-urlencoded');
$req->content("title=$title&uri=$uri&desc=$desc&tags=$tags");
$ua->request($req);
print "done!\n";


Revision [15046]

Edited on 2006-08-03 01:51:35 by BrianKoontz [Added links to IntElf, Freetag stuff]
Additions:
- Gordon Luk (creator of [[http://getluky.net/freetag/ Freetag]], the engine behind Bookmark Manager) has some interesting comments about tagging [[http://tagsonomy.com/index.php/interview-with-gordon-luk-freetag/ here]].
- Here's another [[http://www.integratedelf.com/wiki/doku.php?id=wikkawiki:about Wikka tagging project]] using the [[http://getluky.net/freetag Freetag]] engine. It would be useful to investigate the merging of that project with this one to permit page tagging in addition to bookmark tagging.


Revision [14774]

Edited on 2006-07-06 13:31:51 by BrianKoontz [Added TODO]
Additions:
- Fix PHP warnings w/r/t/ null arrays in ksort, min, max, etc.


Revision [14773]

Edited on 2006-07-05 19:18:59 by BrianKoontz [phpDoc update]
Additions:
/**
* Bookmark manager with tagging
* {@link http://de.licio.us} and {@link http://de.lirio.us}.
* Gordon Luk's Freetag for PHP4 {@link http://www.getluky.net/freetag}
* http://www.gnu.org/copyleft/gpl.html
* @author {@link http://wikkawiki.org/BrianKoontz Brian Koontz} <brian@pongonova.net>
* @copyright Copyright (c) 2006, Brian Koontz <brian@pongonova.net>
* @name BookmarkManager
* @package Actions
* @filesource
* @license http://www.gnu.org/copyleft/gpl.html
* @since Wikka 1.1.6.1
* @version $Id: bookmarks.php,v 1.5 2006/07/05 22:52:20 brian Exp brian $
*/
/* DB connection params */
'db_user' => '',
/* Helper functions */
/**
* wikka_id_to_tagger_id()
/**
* output_tag_cloud()
* private)
/**
* count_objects()
* private)
}
/**
* count_tagged_objects()
* owner (except those marked private)
}
/**
* save_to_current_session()
}
Deletions:
* $Id: bookmarks.php,v 1.1.1.16 2006/06/25 02:43:00 brian Exp brian $
* Tagged object manager
* de.licio.us/de.lirio.us.
/* DB connection params */
'db_user' => 'root',
/* Helper functions */
/* wikka_id_to_tagger_id
/* output_tag_cloud
* private)
/* count_objects
* private)
}
/* count_tagged_objects
* owner (except those marked private)
}
/* save_to_current_session
}


Revision [14698]

Edited on 2006-06-25 00:59:37 by BrianKoontz [CSS support]
Additions:
- ++[[http://wikkawiki.org/BookmarkManager?show_comments=1&showall=1#comment_4505 Move markup to their own div tags]]++
- In order to use the stylesheet, add the following lines to the files indicated (click [[http://wush.net/trac/wikka/ticket/246 here]] for a justification as to why these changes are necessary to the core):
**wikka.config.php:**
**actions/header.php:**
Deletions:
- [[http://wikkawiki.org/BookmarkManager?show_comments=1&showall=1#comment_4505 Move markup to their own div tags]]
- In order to use the stylesheet, add the following lines to the files indicated:
##wikka.config.php:##
##actions/header.php:##


Revision [14697]

Edited on 2006-06-25 00:54:59 by BrianKoontz [CSS support]
Additions:
* $Id: bookmarks.php,v 1.1.1.16 2006/06/25 02:43:00 brian Exp brian $
include_once('libs/Wikka.freetag.class.php');
print "<div class=\"pagination\">";
print $prev_url."  ".$next_url;
print "<div class=\"bookmark_block\">\n";
print "<div class=\"bookmark_title\"><a href=\"".$obj['uri']."\">".$obj['title']."</a></div>\n";
print "<div class=\"bookmark_box\"><div class=\"bookmark_desc\">".($obj['description'] ? $obj['description'] : " ")."</div>\n";
$temp_tag = trim($res['tag']);
$taglist .= "<a href=\"".$link."&tag=".$temp_tag."\">".$temp_tag."</a> ";
print $taglist."by ".$obj['wikka_id']." (created: ".$val['tagged_on'].")\n";
//print "</div><div class=\"clear\"> </div><hr/>";
print "</div></div>\n";
print "<div class=\"pagination\">";
print $prev_url."  ".$next_url;
print "<div class=\"bookmark_block\">\n";
print "<div class=\"bookmark_title\"><a href=\"".$obj['uri']."\">".$obj['title']."</a></div>\n";
print "<div class=\"bookmark_box\"><div class=\"bookmark_desc\">".($obj['description'] ? $obj['description'] : " ")."</div>\n";
print "<a href=\"".$this->href()."&action=delete&object_id=".$val['object_id']."\">delete</a><br/>";
$normtag = trim($res['tag']);
$taglist .= "<a href=\"".$link."&tag=".$normtag."\">".$rawtag."</a> ";
print $taglist."by ".$obj['wikka_id']." (created: ".$val['tagged_on'].")\n";
//print "<div class=\"clear\"> </div><hr/>";
print "</div></div>\n";
print $prev_url."  ".$next_url;
- Save the following file as ##libs/Wikka.freetag.class.php##:
- Create the CSS stylesheet for the action, ##css/bookmarkmgr.css##:
.bookmark_box {
font-size: 85%;
padding: 0px 5px;
color: #666;
background: #EEE;
border: 1px solid #CCC;
.bookmark_block {
border-bottom: 3px solid #DDD;
padding: 8px;
.bookmark_desc {
font-size: 100%;
font-weight: bold;
margin: 0px;
padding: 0px;
.bookmark_title {
font-size: 150%;
font-weight: bold;
padding: 5px 0px;
.pagination {
text-align: center;
margin: 20px auto;
- In order to use the stylesheet, add the following lines to the files indicated:
##wikka.config.php:##
'additional_stylesheets' => 'bookmarkmgr.css',
##actions/header.php:##
// Locate this line...
<link rel="stylesheet" type="text/css" href="css/<?php echo $this->GetConfigValue("stylesheet") ?>" />
// ...and add the following codeblock:
if($this->GetConfigValue("additional_stylesheets") != NULL) {
$sheets = preg_split("/[\s]+/", $this->GetConfigValue("additional_stylesheets"));
foreach($sheets as $sheet) {
echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"css/".$sheet."\" />\n";
Deletions:
* $Id: bookmarks.php,v 1.1.1.14 2006/06/24 06:27:43 brian Exp brian $
include_once('Wikka.freetag.class.php');
print "<p><p>";
print $prev_url." ".$next_url;
print "<h3><a href=\"".$obj['uri']."\">".$obj['title']."</a></h3><br/>";
print "<div class=\"commentsheader\"><b>".$obj['description']."</b>";
print "<br/>";
$tag = trim($res['tag']);
$taglist .= "<a href=\"".$link."&tag=".$tag."\">".$tag."</a> ";
print $taglist."by ".$obj['wikka_id']." (created: ".$val['tagged_on'].")";
print "</div><div class=\"clear\"> </div><hr/>";
print "<p><p>";
print $prev_url." ".$next_url;
print "<h3><a href=\"".$obj['uri']."\">".$obj['title']."</a></h3><br/>";
print "<div class=\"commentsheader\"><b>".$obj['description']."</b>";
print "<a href=\"".$this->href()."&action=delete&object_id=".$val['object_id']."\">delete</a>";
print "<br/>";
$tag = trim($res['tag']);
$taglist .= "<a href=\"".$link."&tag=".$tag."\">".$rawtag."</a> ";
print $taglist."by ".$obj['wikka_id']." (created: ".$val['tagged_on'].")</div>";
print "<div class=\"clear\"> </div><hr/>";
print "<p><p>";
print $prev_url." ".$next_url;
- Save the following file as ##actions/Wikka.freetag.class.php## (needs to be moved to libs/ eventually):


Revision [14696]

Edited on 2006-06-24 18:06:41 by BrianKoontz [Added TODOs]
Additions:
- [[http://wikkawiki.org/BookmarkManager?show_comments=1&showall=1#comment_4505 Move markup to their own div tags]]
- [[http://wikkawiki.org/BookmarkManager?show_comments=1&showall=1#comment_4507 Move helper functions, class extensions to libs/ dir]]


Revision [14690]

Edited on 2006-06-24 03:18:34 by BrianKoontz [Enhancements]
Additions:
- If you want to test bookmarklets, copy and paste this as a bookmark:
%% javascript:location.href='http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks&action=add&uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&wikka_bookmarklet=1'
then go to a webpage and click on the bookmark you just saved.
Deletions:
- If you want to test bookmarklets, save ""<a href="javascript:location.href='http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks&action=add&uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&wikka_bookmarklet=1' ">this link</a>"" as a bookmark, then go to a webpage and click on the bookmark you just saved.


Revision [14689]

Edited on 2006-06-24 03:11:14 by BrianKoontz [Enhancements]
Additions:
- If you want to test bookmarklets, save ""<a href="javascript:location.href='http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks&action=add&uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&wikka_bookmarklet=1' ">this link</a>"" as a bookmark, then go to a webpage and click on the bookmark you just saved.
Deletions:
- If you want to test bookmarklets, save ""<a href=javascript:location.href='http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks&action=add&uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&wikka_bookmarklet=1' >this link</a>"" as a bookmark, then go to a webpage and click on the bookmark you just saved.


Revision [14688]

Edited on 2006-06-24 03:08:46 by BrianKoontz [Enhancements]
Additions:
- If you want to test bookmarklets, save ""<a href=javascript:location.href='http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks&action=add&uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&wikka_bookmarklet=1' >this link</a>"" as a bookmark, then go to a webpage and click on the bookmark you just saved.
Deletions:
- If you want to test bookmarklets, save [[javascript:location.href='http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks&action=add&uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&wikka_bookmarklet=1' this link]] as a bookmark, then go to a webpage and click on the bookmark you just saved.


Revision [14687]

Edited on 2006-06-24 03:07:47 by BrianKoontz [Enhancements]
Additions:
- If you want to test bookmarklets, save [[javascript:location.href='http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks&action=add&uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&wikka_bookmarklet=1' this link]] as a bookmark, then go to a webpage and click on the bookmark you just saved.
Deletions:
- If you want to test bookmarklets, save <a href="javascript:location.href='http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks&action=add&uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&wikka_bookmarklet=1'>this link</a> as a bookmark, then go to a webpage and click on the bookmark you just saved.


Revision [14686]

Edited on 2006-06-24 03:05:56 by BrianKoontz [Enhancements]
Additions:
- If you want to test bookmarklets, save <a href="javascript:location.href='http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks&action=add&uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&wikka_bookmarklet=1'>this link</a> as a bookmark, then go to a webpage and click on the bookmark you just saved.


Revision [14685]

Edited on 2006-06-24 02:48:00 by BrianKoontz [Enhancements]
Additions:
- ++Create bookmarklets such as these (borrowed from [[de.lirio.us]])++: //Implemented...see below for details//
javascript:location.href='http://your.site.com/wiki/wikka.php?wakka=BookmarkPage&action=add&uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&wikka_bookmarklet=1'
(Don't forget to change "your.site.com" as appropriate for your installation!)
TODO: Implement as a popup:
Deletions:
- ++Create bookmarklets such as these (borrowed from [[de.lirio.us]])++: //Implemented...see below for details
post bookmarklet: javascript:location.href='http://de.lirio.us/rubric/post?uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&when_done=go_back'
javascript:location.href='http://de.lirio.us/rubric/post?uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&wikka_bookmarklet=1'


Revision [14684]

Edited on 2006-06-24 02:43:40 by BrianKoontz [Enhancements]
Additions:
- More cool stuff: Support for bookmarklets (small Javascript bookmark that immediately saves the page you are viewing and returns to the page), tag links within link descriptors.
- ++Create bookmarklets such as these (borrowed from [[de.lirio.us]])++: //Implemented...see below for details
- Restricting spam. Some ideas: ++Option to allow only registered users to set bookmarks++ //Implemented//; using "captcha" images to preclude automated scripts
- ++It would be nice if tags in each bookmarks were links pointing to the list of bookmarks with the same tag.++
* $Id: bookmarks.php,v 1.1.1.14 2006/06/24 06:27:43 brian Exp brian $
/* save_to_current_session
* Save various fields to the current session
* Input: Associative array of session tags->values
* Return: Name of session
function save_to_current_session($values) {
foreach($values as $tag=>$value) {
$_SESSION[$tag] = $value;
return $_SESSION['name'];
echo $_SESSION['when_done'];
$values = array();
// Check for request from a bookmarklet...check to see if user
// is logged in...if not, redirect to login page and come back
// here when done
if(isset($_REQUEST['wikka_bookmarklet'])) {
$values['uri'] = $_REQUEST['uri'];
if(isset($_REQUEST['title'])) {
$values['title'] = $_REQUEST['title'];
$values['referrer'] = $_SERVER['HTTP_REFERER'];
$values['when_done'] = "add_link";
save_to_current_session($values);
//session_write_close();
if(!$this->GetUser()) {
$_SESSION['go_back'] = $this->Href(null, $this->MiniHref()."&action=add");
//session_write_close();
$this->Redirect($this->config['base_url']."UserSettings");
// The next clause checks to see if we're returning from a
// login request while trying to execute a bookmarklet
if(isset($_SESSION['when_done']) && ($_SESSION['when_done']=="add_link")) {
$values['uri'] = $_SESSION['uri'];
$values['title'] = $_SESSION['title'];
$_SESSION['when_done'] = "return";
//session_write_close();
// Check to see if it's time to return to the page from which
// the bookmarklet was called
if(isset($_SESSION['when_done']) && ($_SESSION['when_done']=="return")) {
if(isset($_SESSION['referrer'])) {
$referrer = $_SESSION['referrer'];
unset($_SESSION['go_back']);
unset($_SESSION['uri']);
unset($_SESSION['title']);
unset($_SESSION['referrer']);
unset($_SESSION['when_done']);
$this->Redirect($referrer);
}
// Otherwise, go back to tag/link list view
<td><input name="title" size="40" value="<?php echo $values['title']; ?>" /></td>
<td align="right">URI:</td> <td><input name="uri" size="40" value="<?php echo $values['uri']; ?>" /></td> </tr>
<?php echo $_SESSION['when_done']; ?>
<?php echo $_SESSION['referrer']; ?>
print "<h3><a href=\"".$obj['uri']."\">".$obj['title']."</a></h3><br/>";
$link = $url_opts."&tag_offset=".$tag_offset."&tag_limit=".$tag_limit;
$tag = trim($res['tag']);
$taglist .= "<a href=\"".$link."&tag=".$tag."\">".$tag."</a> ";
print "</div><div class=\"clear\"> </div><hr/>";
print "<h3><a href=\"".$obj['uri']."\">".$obj['title']."</a></h3><br/>";
$link = $url_opts."&tag_offset=".$tag_offset."&tag_limit=".$tag_limit;
$tag = trim($res['tag']);
$rawtag = trim($res['raw_tag']);
$taglist .= "<a href=\"".$link."&tag=".$tag."\">".$rawtag."</a> ";
print "<div class=\"clear\"> </div><hr/>";

==Bookmarklet support==
BookmarkManager now supports "bookmarklets," which is a small snippet of Javascript saved as a browser bookmark that allows you to bookmark a webpage "on the fly." The following patch must be applied to ##actions/usersettings.php## if you want to restrict bookmarklets to registered users (recommended):
%%(sh)
===================================================================
RCS file: RCS/usersettings.php,v
retrieving revision 1.1
diff -u -r1.1 usersettings.php
--- usersettings.php 2006/06/18 06:03:03 1.1
+++ usersettings.php 2006/06/24 06:40:13
@@ -1,5 +1,7 @@
<?php
/**
+ * $Id: usersettings.php,v 1.5 2006/06/24 06:40:07 brian Exp brian $
+ *
* Display a form to register, login and change user settings.
*
* @package Actions
@@ -334,6 +336,13 @@
// is user trying to log in or register?
if (isset($_POST['action']) && ($_POST['action'] == 'login'))
{
+ // Login request was redirected from elsewhere...let's make
+ // sure to go back if requested by setting $referrer
+ $referrer = null;
+ if(isset($_SESSION['go_back'])) {
+ $referrer = $_SESSION['go_back'];
+ }
+
// if user name already exists, check password
if (isset($_POST['name']) && $existingUser = $this->LoadUser($_POST['name']))
{
@@ -349,7 +358,11 @@
break;
default:
$this->SetUser($existingUser);
- $this->Redirect($this->href());
+ if($referrer == null)
+ $this->Redirect($this->href());
+ else {
+ $this->Redirect($referrer);
+ }
}
}
else // otherwise, proceed to registration
@@ -417,7 +430,11 @@

// log in
$this->SetUser($this->LoadUser($name));
- $this->Redirect($this->href('', '', 'registered=true'));
+ if($referrer == null)
+ $this->Redirect($this->href('', '', 'registered=true'));
+ else {
+ $this->Redirect($referrer);
+ }
}
}
@@ -542,4 +559,4 @@
<?php
}
Save the following as a bookmark in your browser:
javascript:location.href='http://de.lirio.us/rubric/post?uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&wikka_bookmarklet=1'
Deletions:
- Create bookmarklets such as these (borrowed from [[de.lirio.us]]):
- Restricting spam. Some ideas: Option to allow only registered users to set bookmarks; using "captcha" images to preclude automated scripts
- It would be nice if tags in each boorkmarks were links pointing to the list of bookmarks with the same tag.
* $Id: bookmarks.php,v 1.1.1.10 2006/06/05 03:27:42 brian Exp brian $
<td><input name="title" size="40"/></td>
<td align="right">URI:</td>
<td><input name="uri" size="40"/></td>
<?php
print "<a href=\"".$obj['uri']."\">".$obj['title']."</a><br/>";
$taglist .= trim($res['tag'])." ";
print "</div><div class=\"clear\"> </div>";
print "<a href=\"".$obj['uri']."\">".$obj['title']."</a><br/>";
$taglist .= trim($res['raw_tag'])." ";
print "<div class=\"clear\"> </div>";


Revision [14674]

Edited on 2006-06-22 12:23:08 by BrianKoontz [Added TODO]
Additions:
- It would be nice if tags in each boorkmarks were links pointing to the list of bookmarks with the same tag.


Revision [14667]

Edited on 2006-06-22 04:15:34 by BrianKoontz [Minor edit]

No Differences

Revision [14666]

Edited on 2006-06-22 04:05:07 by BrianKoontz [Minor edit]
Additions:
An integrated bookmark manager in the spirit of [[de.lirio.us]]. //Note:// de.lirio.us will be shutting down within the month (end of July 2006), so if you plan on exporting your de.lirio.us bookmarks, better do it soon!
- More enhancements and modifications: Numeric tags, link pagination, tag pagination, tag/link editing and deletion.
Deletions:
An integrated bookmark manager in the spirit of [[de.lirio.us]].
- More enhancements and modifications: Numeric tags, link pagination, tag pagination, tag/link editing and deletion.


Revision [14497]

Edited on 2006-06-07 00:58:30 by BrianKoontz [Added cats]
Additions:
CategoryDevelopmentActions
CategoryUserContributions


Revision [14465]

Edited on 2006-06-04 23:42:51 by BrianKoontz [Code enhancements]
Additions:
* $Id: Wikka.freetag.class.php,v 1.6 2006/06/05 03:32:33 brian Exp brian $
$this->_normalized_valid_chars = '@_a-zA-Z0-9';
/*******************************************************************
* get_tag_cloud_html_with_limits
* Extension to get_tag_cloud_html() that permits specifying
* an offset and limit (useful for presenting more tags than
* can be displayed at one time).
* See get_tag_cloud_html() comments for description of this
* function.
* @param int Specify starting record (default: 0)
* @param int The maximum number of tags to return. (default: 100)
* @param int The minimum font size in the cloud. (default: 10)
* @param int The maximum number of tags to return. (default: 20)
* @param string The "units" for the font size (i.e. 'px', 'pt',
* 'em') (default: px)
* @param string The class to use for all spans in the cloud.
* (default: cloud_tag)
* @param string The tag page URL (default: /tag/)
* @return string Returns an HTML snippet that can be used
* directly as a tag cloud.
function get_tag_cloud_html_with_limits($offset = 0, $limit = 100, $min_font_size = 10,
$max_font_size = 20, $font_units = 'px', $span_class = 'cloud_tag',
$tag_page_url = '/tag/', $tagger_id = NULL) {
$tag_list = $this->get_tag_cloud_tags_with_limits($offset, $limit, $tagger_id);
// Get the maximum qty of tagged objects in the set
$max_qty = max(array_values($tag_list));
// Get the min qty of tagged objects in the set
$min_qty = min(array_values($tag_list));
// For ever additional tagged object from min to max, we add
// $step to the font size.
$spread = $max_qty - $min_qty;
if (0 == $spread) { // Divide by zero
$spread = 1;
$step = ($max_font_size - $min_font_size)/($spread);
// Since the original tag_list is alphabetically ordered,
// we can now create the tag cloud by just putting a span
// on each element, multiplying the diff between min and qty
// by $step.
$cloud_html = '';
$cloud_spans = array();
foreach ($tag_list as $tag => $qty) {
//if(strpos($tag, "private") !== false) continue;
$size = $min_font_size + ($qty - $min_qty) * $step;
$cloud_span[] = '<span class="' . $span_class . '"
style="font-size: '. $size . $font_units . '"><a href="'.$tag_page_url
. $tag . '">' . htmlspecialchars(stripslashes($tag)) . '</a></span>';
$cloud_html = join("\n ", $cloud_span);
return $cloud_html;
/*
* get_tag_cloud_tags_with_limits
* Extension to get_tag_cloud_tags() that permits specifying
* an offset and limit (useful for presenting more tags than
* can be displayed at one time). Also, tags are returned
* in true alphanumeric sequence across the entire record set,
* rather than across the most popular tags.
* See get_tag_cloud_tags() comments for description of this
* function.
* @param int Specify starting record (default: 0)
* @param int The maximum number of tags to return (default: 100)
* @param int Tag owner (default: NULL)
* @return array Returns an array where the keys are normalized
* tags, and the
* values are numeric quantity of objects tagged with that tag.
function get_tag_cloud_tags_with_limits($offset = 0, $limit = 100, $tagger_id = NULL) {
if(isset($tagger_id) && ($tagger_id > 0)) {
$sql = "SELECT tag, COUNT(object_id) AS quantity
FROM ${prefix}freetags INNER JOIN
${prefix}freetagged_objects
ON (${prefix}freetags.id = tag_id)
WHERE 1
$tagger_sql
GROUP BY tag
ORDER BY tag ASC
LIMIT $offset, $limit
";
$retarr[$rs->fields['tag']] = $rs->fields['quantity'];
ksort($retarr);
/**
* count_unique_tags
* Returns the total number of unique tags in the system.
*
* @param int The unique ID of the person to restrict results to.
*
* @return int Returns the count
*/
function count_unique_tags($tagger_id = NULL) {
if(isset($tagger_id) && ($tagger_id > 0)) {
$sql = "SELECT DISTINCT tag_id, COUNT(*) as count
FROM ${prefix}freetags INNER JOIN ${prefix}freetagged_objects ON (id = tag_id)
$tagger_sql
GROUP BY NULL
";
if(!$rs->EOF) {
return $rs->fields['count'];
return false;
- Add the ""{{bookmarks}}"" action code to a new page
Deletions:
* $Id: Wikka.freetag.class.php,v 1.3 2006/05/29 16:51:23 brian Exp brian $
/**
* get_most_recent_objects
* This function returns the most recent object ids in the
* freetag system, with offset and limit support for
* pagination. It also supports restricting to an individual
* user. Call it with no parameters for a list of 25 most
* recent tags.
*
* @param int The unique ID of the person to restrict results to.
* @param string Tag to filter by
* @param int The offset of the object to start at.
* @param int The number of object ids to return in the result set.
* @return array Returns a PHP array with object ids ordered by
* timestamp descending.
* Each element is an associative array with the following elements:
* - 'object_id' => Object id
* - 'tagged_on' => The timestamp of each object id
function get_most_recent_objects($tagger_id = NULL, $tag = NULL, $offset = 0, $limit = 25) {
if(isset($tagger_id)) {
$sql = "";
if(!$tag) {
$sql = "SELECT DISTINCT object_id, tagged_on FROM
${prefix}freetagged_objects
WHERE 1
$tagger_sql
ORDER BY tagged_on DESC
LIMIT $offset, $limit ";
$tag = $db->qstr($tag, get_magic_quotes_gpc());
$sql = "SELECT DISTINCT object_id, tagged_on
FROM ${prefix}freetagged_objects INNER JOIN ${prefix}freetags ON (tag_id = id)
WHERE tag = $tag
$tagger_sql
ORDER BY tagged_on DESC
LIMIT $offset, $limit ";
$retarr = array();
$retarr[] = array(
'object_id' => $rs->fields['object_id'],
'tagged_on' => $rs->fields['tagged_on']
);
- (Optional) After line 918 in ##freetag.class.php##, add the following line to prevent @private tags from cluttering up the landscape:
foreach ($tag_list as $tag => $qty) {
// Add the following line:
if(strpos($tag, "private") !== false) continue;
$size = $min_font_size + ($qty - $min_qty) * $step;
- Add the ""{{bookmarks}}"" handler code to a new page


Revision [14464]

Edited on 2006-06-04 23:40:30 by BrianKoontz [Code enhancements]
Additions:
- More enhancements and modifications: Numeric tags, link pagination, tag pagination, tag/link editing and deletion.
- Install a copy of [[http://www.getluky.net/freetag/ Freetag]] in **3rdparty/plugins/** (version 0.250 or better)
* $Id: bookmarks.php,v 1.1.1.10 2006/06/05 03:27:42 brian Exp brian $
function output_tag_cloud($freetag,$tag_page_url,$tagger_id=NULL,$header=NULL,$tag_offset=0,$tag_limit=100) {
$tag_count = $freetag->count_unique_tags($tagger_id);
print $header." (".$tag_count."):<br/>";
print $freetag->get_tag_cloud_html_with_limits($tag_offset,$tag_limit,10,20,'px','cloud_tag',$tag_page_url."&tag_offset=".$tag_offset."&tag_limit=".$tag_limit."&tag=",$tagger_id);
$prev_offset = $tag_offset - $tag_limit;
if($tag_offset > 0) {
$prev_url = "<a href=\"".$tag_page_url."&tag_offset=".$prev_offset."&tag_limit=".$tag_limit."\"><<</a>";
$next_offset = $tag_offset + $tag_limit;
if($next_offset < $tag_count - 1) {
$next_url = "<a href=\"".$tag_page_url."&tag_offset=".$next_offset."&tag_limit=".$tag_limit.$tag_url."\">>></a>";

// Converts numeric tag "123" to "123_" to facilitate
// alphanumeric sorting (otherwise, PHP converts string to
// true integer).
$tags = preg_split('/\s+/', $_REQUEST['tags'], -1, PREG_SPLIT_NO_EMPTY);
$tags = preg_replace('/^([0-9]+)$/', "$1_", $tags);
$tags = implode(" ", $tags);
$freetag->tag_object($tagger_id, $last_id['last_insert_id()'], $tags);
$tag_offset = 0;
if(isset($_REQUEST['tag_offset'])) {
$tag_offset = $_REQUEST['tag_offset'];
$tag_limit = 100;
if(isset($_REQUEST['tag_limit'])) {
$tag_limit = $_REQUEST['tag_limit'];
$url_opts = $this->href()."&action=list_all";
output_tag_cloud($freetag,$url_opts,NULL,"All tags",$tag_offset,$tag_limit);
$tag_offset = 0;
if(isset($_REQUEST['tag_offset'])) {
$tag_offset = $_REQUEST['tag_offset'];
$tag_limit = 100;
if(isset($_REQUEST['tag_limit'])) {
$tag_limit = $_REQUEST['tag_limit'];
$url_opts = $this->href()."&action=list_yours";
output_tag_cloud($freetag,$url_opts,$tagger_id,"Your tags",$tag_offset,$tag_limit);
Deletions:
- Install a copy of [[http://www.getluky.net/freetag/ Freetag]] in **3rdparty/plugins/** (I am using v0.240 for testing)
* $Id: bookmarks.php,v 1.1.1.8 2006/05/31 09:01:47 brian Exp brian $
function output_tag_cloud($freetag,$tag_page_url,$tagger_id=NULL,$header=NULL) {
print $header."<br/>";
print $freetag->get_tag_cloud_html(100,10,20,'px','cloud_tag',$tag_page_url,$tagger_id);
$freetag->tag_object($tagger_id, $last_id['last_insert_id()'], $_REQUEST['tags']);
$url_opts = $this->href()."&action=list_all&tag=";
output_tag_cloud($freetag,$url_opts,NULL,"All tags:");
$url_opts = $this->href()."&action=list_yours&tag=";
output_tag_cloud($freetag,$url_opts,$tagger_id,"Your tags:");


Revision [14447]

Edited on 2006-06-02 22:37:57 by BrianKoontz [Added link]
Additions:
<<Try it out! Beta version with latest code [[http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks here]].
<<::c::


Revision [14431]

Edited on 2006-06-01 17:55:49 by BrianKoontz [TODO additions]
Additions:
- Restricting spam. Some ideas: Option to allow only registered users to set bookmarks; using "captcha" images to preclude automated scripts
Deletions:
- Restricting spam. Some ideas: Option to allow only registered users to set bookmarks; supplying a random number for each add request to preclude calling the add action from a script


Revision [14430]

Edited on 2006-06-01 14:48:38 by BrianKoontz [TODO addition]
Additions:
- Restricting spam. Some ideas: Option to allow only registered users to set bookmarks; supplying a random number for each add request to preclude calling the add action from a script


Revision [14417]

Edited on 2006-05-31 22:10:10 by BrianKoontz [Updated TODO list]
Deletions:
[[javascript:location.href='http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks&action=add?uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&when_done=go_back' post Wikka bookmark]]


Revision [14416]

Edited on 2006-05-31 21:47:04 by BrianKoontz [Updated TODO list]
Additions:
[[javascript:location.href='http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks&action=add?uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&when_done=go_back' post Wikka bookmark]]


Revision [14415]

Edited on 2006-05-31 21:42:33 by BrianKoontz [Updated TODO list]
Additions:
- ++This code is most likely not yet safe for a production environment!++ //I've worked to secure all data passed in from GET/POST requests, as well as from Wikka itself in some cases. I'm hoping a few more folks have time to look over the code for any obvious security weaknesses.//
- ++how about something like my tags / all tags?++ //List all/list yours now provides this functionality//
- ++Please put the wikka_freetag class in a file like Wikka.freetag.class.php in the new \libs directory.++
- $freetag_options -> why not use the params provided by wikka ($this->mysql_host, etc.)?
Deletions:
- **This code is most likely not yet safe for a production environment!** I'm hoping a few more folks have time to look over the code for any obvious security weaknesses.
- ++how about something like my tags / all tags?++ (List all/list yours provides this functionality)
- ++1. please put the wikka_freetag class in a file like Wikka.freetag.class.php in the new \libs directory.++ 2. $freetag_options -> why not use the params provided by wikka ($this->mysql_host, etc.)?


Revision [14390]

Edited on 2006-05-31 05:07:55 by BrianKoontz [Code updates]
Additions:
- ++Paging++, user prefs
Deletions:
- Paging, user prefs


Revision [14389]

Edited on 2006-05-31 05:05:45 by BrianKoontz [Code updates]
Additions:
* $Id: bookmarks.php,v 1.1.1.8 2006/05/31 09:01:47 brian Exp brian $
$wikka_id = mysql_real_escape_string($wikka_id);
/* count_objects
* Returns a count of objects (bookmarks, links, etc.)
* NOTE: Set $wikka_id to NULL for all objects (except those marked
function count_objects($wikka_id, $obj, $freetag_options) {
if(isset($wikka_id)) {
$wikka_sql = "AND wikka_id = '".mysql_real_escape_string($wikka_id)."'";
$wikka_sql = "AND private = 0";
$res = $obj->LoadSingle("select COUNT(*) as count from ".$freetag_options['db_name'].".freetag_bookmarks where 1 ".$wikka_sql.";");
$count = $res['count'];
return $count;
}
/* count_tagged_objects
* Returns a count of tagged objects (bookmarks, links, etc.)
* NOTE: Set $wikka_id to NULL for all taggedobjects regardless of
* owner (except those marked private)
function count_tagged_objects($wikka_id, $tag, $obj, $freetag_options, $freetag) {
$num_objs = count_objects($wikka_id, $obj, $freetag_options);
$tagger_id = wikka_id_to_tagger_id($wikka_id, $obj, $freetag_options);
$ids = $freetag->get_most_recent_objects($tagger_id, $tag, 0, $num_objs);
return count($ids);
}
$all_obj_count = count_objects(NULL, $this, $freetag_options);
$your_obj_count = count_objects($wikka_id, $this, $freetag_options);
echo "<a href=\"".$this->href()."&action=list_all"."\">List all</a> (".$all_obj_count.") | ".
"<a href=\"".$this->href()."&action=list_yours"."\">List yours</a> (".$your_obj_count.") | ".
"wikka_id = '".mysql_real_escape_string($wikka_id)."', ".
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="edit") && (isset($_REQUEST['object_id']))) {
$object_id = $_REQUEST['object_id'];
"wikka_id = '".mysql_real_escape_string($wikka_id)."', ".
"where bookmark_id = ".mysql_real_escape_string($object_id)." ".
$object_id = $_REQUEST['object_id'];
$obj = $this->LoadSingle("select bookmark_id,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".mysql_real_escape_string($object_id).";");
$this->Query("delete from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".mysql_real_escape_string($object_id)." limit 1;");
$offset = 0;
if(isset($_REQUEST['offset'])) {
$offset = $_REQUEST['offset'];
$limit = 25;
if(isset($_REQUEST['limit'])) {
$limit = $_REQUEST['limit'];
$tag = "";
$ids = $freetag->get_most_recent_objects(NULL,$_REQUEST['tag'],$offset,$limit);
$tag = $_REQUEST['tag'];
$ids = $freetag->get_most_recent_objects(NULL,NULL,$offset,$limit);
$obj = $this->LoadSingle("select title,uri,description,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$val['object_id']." and private = 0;");
if(!isset($obj)) {
continue;
print "<a href=\"".$obj['uri']."\">".$obj['title']."</a><br/>";
print "<div class=\"commentsheader\"><b>".$obj['description']."</b>";
print "<br/>";
$taglist = "";
foreach($tags as $idx=>$res) {
$taglist .= trim($res['tag'])." ";
print $taglist."by ".$obj['wikka_id']." (created: ".$val['tagged_on'].")";
print "</div><div class=\"clear\"> </div>";
// Display pagination links
$obj_count = 0;
$tag_url = "";
if($tag != "") {
$obj_count = count_tagged_objects(NULL, $tag, $this, $freetag_options, $freetag);
$tag_url = "&tag=".$tag;
$obj_count = count_objects(NULL, $this, $freetag_options);
$prev_offset = $offset - $limit;
$prev_url = "";
if($prev_offset < 0) {
$prev_offset = 0;
if($offset > 0) {
$prev_url = "<a href=\"".$this->href()."&action=list_all&offset=".$prev_offset."&limit=".$limit.$tag_url."\"><<</a>";
$next_offset = $offset + $limit;
$next_url = "";
if($next_offset < $obj_count - 1) {
$next_url = "<a href=\"".$this->href()."&action=list_all&offset=".$next_offset."&limit=".$limit.$tag_url."\">>></a>";
print "<p><p>";
print "<div class=\"center\">";
print $prev_url." ".$next_url;
print "</div>";
$offset = 0;
if(isset($_REQUEST['offset'])) {
$offset = $_REQUEST['offset'];
$limit = 25;
if(isset($_REQUEST['limit'])) {
$limit = $_REQUEST['limit'];
$tag = "";
$ids = $freetag->get_most_recent_objects($tagger_id, $_REQUEST['tag'], $offset, $limit);
$tag = $_REQUEST['tag'];
$ids = $freetag->get_most_recent_objects($tagger_id, NULL, $offset, $limit);
$obj = $this->LoadSingle("select title,uri,description,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$val['object_id'].";");
print "<a href=\"".$obj['uri']."\">".$obj['title']."</a><br/>";
print "<div class=\"commentsheader\"><b>".$obj['description']."</b>";
print " <a href=\"".$this->href()."&action=edit&object_id=".$val['object_id']."\">edit</a>";
print "|";
print "<a href=\"".$this->href()."&action=delete&object_id=".$val['object_id']."\">delete</a>";
print "<br/>";
$taglist = "";
foreach($tags as $idx=>$res) {
$taglist .= trim($res['raw_tag'])." ";
print $taglist."by ".$obj['wikka_id']." (created: ".$val['tagged_on'].")</div>";
print "<div class=\"clear\"> </div>";
// Display pagination links
$obj_count = 0;
$tag_url = "";
if($tag != "") {
$obj_count = count_tagged_objects($wikka_id, $tag, $this, $freetag_options, $freetag);
$tag_url = "&tag=".$tag;
$obj_count = count_objects($wikka_id, $this, $freetag_options);
$prev_offset = $offset - $limit;
$prev_url = "";
if($prev_offset < 0) {
$prev_offset = 0;
if($offset > 0) {
$prev_url = "<a href=\"".$this->href()."&action=list_yours&offset=".$prev_offset."&limit=".$limit.$tag_url."\"><<</a>";
$next_offset = $offset + $limit;
$next_url = "";
if($next_offset < $obj_count - 1) {
$next_url = "<a href=\"".$this->href()."&action=list_yours&offset=".$next_offset."&limit=".$limit.$tag_url."\">>></a>";
print "<p><p>";
print "<div class=\"center\">";
print $prev_url." ".$next_url;
print "</div>";
# $Id: importDelirious.pl,v 1.3 2006/05/31 04:43:19 brian Exp brian $
$bookmark_page = "Bookmarks";
$add_url = $base_url."wikka.php?wakka=".$bookmark_page."&action=add";
Deletions:
* $Id: bookmarks.php,v 1.1.1.7 2006/05/29 16:49:32 brian Exp brian $
echo "<a href=\"".$this->href()."&action=list_all"."\">List all</a> | ".
"<a href=\"".$this->href()."&action=list_yours"."\">List yours</a> | ".
"wikka_id = '".$wikka_id."', ".
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="edit") && ($object_id = mysql_real_escape_string($_REQUEST['object_id']))) {
"wikka_id = '".$wikka_id."', ".
"where bookmark_id = ".$object_id." ".
$object_id = mysql_real_escape_string($_REQUEST['object_id']);
$obj = $this->LoadSingle("select bookmark_id,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$object_id.";");
$this->Query("delete from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$object_id." limit 1;");
$ids = $freetag->get_most_recent_objects(NULL,$_REQUEST['tag']);
$ids = $freetag->get_most_recent_objects();
$objs = $this->LoadAll("select title,uri,description,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$val['object_id']." and private = 0;");
foreach($objs as $idx=>$obj) {
print "<a href=\"".$obj['uri']."\">".$obj['title']."</a><br/>";
print "<div class=\"commentsheader\"><b>".$obj['description']."</b>";
print "<br/>";
$taglist = "";
foreach($tags as $idx=>$res) {
$taglist .= trim($res['tag'])." ";
}
print $taglist."by ".$obj['wikka_id']." (created: ".$val['tagged_on'].")";
print "</div><div class=\"clear\"> </div>";
$ids = $freetag->get_most_recent_objects($tagger_id,$_REQUEST['tag']);
$ids = $freetag->get_most_recent_objects($tagger_id);
$objs = $this->LoadAll("select title,uri,description,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$val['object_id'].";");
foreach($objs as $idx=>$obj) {
print "<a href=\"".$obj['uri']."\">".$obj['title']."</a><br/>";
print "<div class=\"commentsheader\"><b>".$obj['description']."</b>";
print " <a href=\"".$this->href()."&action=edit&object_id=".$val['object_id']."\">edit</a>";
print "|";
print "<a href=\"".$this->href()."&action=delete&object_id=".$val['object_id']."\">delete</a>";
print "<br/>";
$taglist = "";
foreach($tags as $idx=>$res) {
$taglist .= trim($res['raw_tag'])." ";
}
print $taglist."by ".$obj['wikka_id']." (created: ".$val['tagged_on'].")</div>";
print "<div class=\"clear\"> </div>";
# $Id: importDelirious.pl,v 1.2 2006/05/30 03:27:36 brian Exp brian $
$add_url = $base_url."wikka.php?wakka=Bookmarks&action=add";


Revision [14387]

Edited on 2006-05-30 15:12:56 by BrianKoontz [More TODOs]
Additions:
- Progress is going well. Add/edit/delete capabilities have been added. Two Perl scripts (see below) have been provided that allow you to export your bookmarks from de.lirio.us and import them into BookmarkManager. Beta currently running under Wikka 1.1.6.2-alpha. A page of 25 links (out of 200+) and about 100 tags renders in approximately 1.1 seconds.
- User tags should be displayed as raw tags.
Deletions:
- Progress is going well. Add/edit/delete capabilities have been added. Two Perl scripts (see below) have been provided that allow you to export your bookmarks from de.lirio.us and import them into BookmarkManager.


Revision [14383]

Edited on 2006-05-29 23:32:10 by BrianKoontz [Added de.lirio.us import scripts]
Additions:
- Progress is going well. Add/edit/delete capabilities have been added. Two Perl scripts (see below) have been provided that allow you to export your bookmarks from de.lirio.us and import them into BookmarkManager.
- **This code is most likely not yet safe for a production environment!** I'm hoping a few more folks have time to look over the code for any obvious security weaknesses.
- ++it would be nice to have a link for the add-form near list yours.++
- Search functionality
==Other stuff==
- The two Perl scripts that follow can be used to import bookmarks from de.lirio.us. Use your browser to save a page of bookmarks as an HTML file (you might have to save multiple pages; that's OK, the script can handle it). Export the HTML data into text format:
parseDelirious.pl file1.html file2.html file3.html > myLinks.txt
Change the ##$base_url##, ##$wikiname##, and ##$password## global vars in ##importDelirious.pl##, then run against the file created in the previous step:
importDelirious.pl myLinks.txt
##parseDelirious.pl##
%%(perl)
#! /usr/bin/perl
#
# $Id: parseDelirious.pl,v 1.2 2006/05/30 03:27:21 brian Exp brian $
#
# parseDelirious.pl - Parses a de.lirio.us screen dump (as saved by
# Firefox)
#
#####################################################################
require HTML::TreeBuilder;
foreach my $filename(@ARGV) {
my $tree = HTML::TreeBuilder->new;
$tree->parse_file($filename);
$tree->elementify();
@nodes = $tree->look_down("class","xfolkentry");
foreach $node(@nodes) {
$_ = $node->look_down("class","uri");
$link = $_->extract_links();
$title = $link->[0]->[1]->as_text();
print "Title: $title\n";
print "URI: $link->[0]->[0]\n";
# Get description, if any
$_=$node->look_down("class","extended");
print "Desc: ";
if($_) {
$desc = $_->as_text();
print "$desc";
print "\n";
# A de.lirio.us export quirk prevents some tags from
# displaying; default these to "@private" for later review
@_ = $node->look_down("class","tag");
print "Tags: ";
if($#_ < 0) {
print "\@private";
foreach $tagnode(@_) {
$link = $tagnode->extract_links();
$tag = $link->[0]->[1]->as_text();
print "$tag ";
}
print "\n\n\n";
$tree = $tree->delete;
}
##importDelirious.pl##
%%(perl)
#! /usr/bin/perl
#
# $Id: importDelirious.pl,v 1.2 2006/05/30 03:27:36 brian Exp brian $
#
# importDelirious.pl -- Imports file created by parseDelirious.pl
#
# Usage: importDelirious.pl exportFile
#
#####################################################################
require LWP::UserAgent;
require HTTP::Cookies;
###Global###
$base_url = "http://some.url.com/wiki/";
$wikiname = "YourName";
$password = "yourpassword";
$ua = LWP::UserAgent->new;
$cookie_jar = HTTP::Cookies->new(file => "lwpcookies.txt",
autosave => 1);
$ua->cookie_jar($cookie_jar);
# Login
$login_url = $base_url."wikka.php?wakka=UserSettings";
my $req = HTTP::Request->new(POST=>"$login_url");
$req->content_type('application/x-www-form-urlencoded');
$req->content("name=$wikiname&password=$password&action=login&wakka=UserSettings");
my $res = $ua->request($req);
$cookie_jar->extract_cookies($res);
# Import
open(IN, "<$ARGV[0]") || die "Can't open $ARGV[0] for reading!";
# Set autoflush so progress is displayed
my $oldfh = select(STDOUT); $| = 1; select($oldfh);
print "Importing...";
while(<IN>) {
print ".";
next until /Title: /;
chomp;
$title = (split(": ",$_))[1];
$_ = <IN>;
chomp;
$uri = (split(": ",$_))[1];
$_ = <IN>;
chomp;
$desc = (split(": ",$_))[1];
$_ = <IN>;
chomp;
$tags = (split(": ",$_))[1];
$add_url = $base_url."wikka.php?wakka=Bookmarks&action=add";
$req = HTTP::Request->new(POST=>"$add_url");
$req->content_type('application/x-www-form-urlencoded');
$req->content("title=$title&uri=$uri&desc=$desc&tags=$tags");
$ua->request($req);
}
print "done!\n";
Deletions:
- **This code is most likely not yet safe for a production environment!** There is still some user data validation that needs to be done.
- it would be nice to have a link for the add-form near list yours.
-


Revision [14379]

Edited on 2006-05-29 13:22:20 by BrianKoontz [Code updates]
Additions:
++Indicates issues that have been addressed++
- ++how about something like my tags / all tags?++ (List all/list yours provides this functionality)
- ++Need to fix logic so "your" tags only display "your" links (also, identify tag cloud as "all" or "yours")++
- ++1. please put the wikka_freetag class in a file like Wikka.freetag.class.php in the new \libs directory.++ 2. $freetag_options -> why not use the params provided by wikka ($this->mysql_host, etc.)?
- ++Provide edit/delete functionality++
-
-- $Id: freetag.sql,v 1.2 2006/05/29 17:11:44 brian Exp brian $
* $Id: bookmarks.php,v 1.1.1.7 2006/05/29 16:49:32 brian Exp brian $
include_once('Wikka.freetag.class.php');
function output_tag_cloud($freetag,$tag_page_url,$tagger_id=NULL,$header=NULL) {
if($header) {
print $header."<br/>";
print $freetag->get_tag_cloud_html(100,10,20,'px','cloud_tag',$tag_page_url,$tagger_id);
"<a href=\"".$this->href()."&action=list_yours"."\">List yours</a> | ".
"<a href=\"".$this->href()."&action=add"."\">Add</a><br/>";
if(!isset($_REQUEST['action'])) {
// Display all tags at start of session
$this->Redirect($this->href()."&action=list_all");
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="add")) {
if(trim($_REQUEST['tags']) != "") {
// Private bookmark?
$private = 0;
if(strpos(trim($_REQUEST['tags']), "@private") !== false)
$private = 1;
$this->Query("insert ".$freetag_options['db_name'].".freetag_bookmarks set ".
"title = '".mysql_real_escape_string($_REQUEST['title'])."', ".
"uri = '".mysql_real_escape_string($this->cleanUrl($_REQUEST['uri']))."', ".
"wikka_id = '".$wikka_id."', ".
"private = '".$private."', ".
"description = '".mysql_real_escape_string($_REQUEST['desc'])."';");
$last_id = $this->LoadSingle("select last_insert_id();");
$freetag->tag_object($tagger_id, $last_id['last_insert_id()'], $_REQUEST['tags']);
$this->Redirect($this->href()."&action=list_yours");
} else {
// Display add form
print($this->FormOpen());
?>
<input type="hidden" name="action" value="add" />
<table>
<tr>
<td align="right">Title:</td>
<td><input name="title" size="40"/></td>
</tr>
<tr>
<td align="right">URI:</td>
<td><input name="uri" size="40"/></td>
</tr>
<tr>
<td align="right">Description:</td>
<td><input name="desc" size="40"/></td>
</tr>
<tr>
<td align="right"></td>
<td><?php echo $this->Format("Use blank space between tags, include \"@private\" for private bookmark"); ?></td>
</tr>
<tr>
<td align="right">Tags:</td>
<td><input name="tags" size="40"/></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Add" size="40" /></td>
</tr>
</table>
<?php
print($this->FormClose());
// Edit entry
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="edit") && ($object_id = mysql_real_escape_string($_REQUEST['object_id']))) {
$obj = $this->LoadSingle("select bookmark_id,title,uri,description,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$object_id.";");
// Is the logged-in user the owner?
if($wikka_id != $obj['wikka_id']) {
$this->Redirect($this->href()."&action=list_all");
if(isset($_REQUEST['modify']) && trim($_REQUEST['tags']) != "") {
// Delete all tags, then re-tag
$freetag->delete_all_object_tags_for_user($tagger_id,$object_id);
// Private bookmark?
$private = 0;
if(strpos(trim($_REQUEST['tags']), "@private") !== false)
$private = 1;
$this->Query("update ".$freetag_options['db_name'].".freetag_bookmarks set ".
"title = '".mysql_real_escape_string($_REQUEST['title'])."', ".
"uri = '".mysql_real_escape_string($this->cleanUrl($_REQUEST['uri']))."', ".
"wikka_id = '".$wikka_id."', ".
"private = '".$private."', ".
"description = '".mysql_real_escape_string($_REQUEST['desc'])."' ".
"where bookmark_id = ".$object_id." ".
"limit 1;");
$freetag->tag_object($tagger_id, $obj['bookmark_id'], $_REQUEST['tags']);
$this->Redirect($this->href()."&action=list_yours");
// Display add form
print($this->FormOpen());
?>
<input type="hidden" name="action" value="edit" />
<input type="hidden" name="modify" value="yes" />
<input type="hidden" name="object_id" value="<?php print $object_id ?>" />
<table>
<tr>
<td align="right">Title:</td>
<td><input name="title" size="40" value="<?php print $obj['title']; ?>"/></td>
</tr>
<tr>
<td align="right">URI:</td>
<td><input name="uri" size="40" value="<?php print $obj['uri']; ?>"/></td>
</tr>
<tr>
<td align="right">Description:</td>
<td><input name="desc" size="40" value="<?php print $obj['description']; ?>"/></td>
</tr>
<tr>
<td align="right"></td>
<td><?php echo $this->Format("Use blank space between tags, include \"@private\" for private bookmark"); ?></td>
</tr>
<tr>
<td align="right">Tags:</td>
<?php
$tags = $freetag->get_tags_on_object($object_id);
$taglist .= trim($res['raw_tag'])." ";
?>
<td><input name="tags" size="40" value="<?php print $taglist?>"/></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Edit" size="40" /></td>
</tr>
</table>
<?php
print($this->FormClose());
// Delete entry
if(isset($_REQUEST['action']) && $_REQUEST['action']=="delete") {
$object_id = mysql_real_escape_string($_REQUEST['object_id']);
$obj = $this->LoadSingle("select bookmark_id,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$object_id.";");
// Is the logged-in user the owner?
if($wikka_id != $obj['wikka_id']) {
$this->Redirect($this->href()."&action=list_all");
// Delete all tags first...
$freetag->delete_all_object_tags_for_user($tagger_id,$object_id);
// ...then delete object
$this->Query("delete from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$object_id." limit 1;");
$this->Redirect($this->href()."&action=list_yours");
$url_opts = $this->href()."&action=list_all&tag=";
output_tag_cloud($freetag,$url_opts,NULL,"All tags:");
$ids = "";
if(isset($_REQUEST['tag']) && $_REQUEST['tag'] != "") {
$ids = $freetag->get_most_recent_objects(NULL,$_REQUEST['tag']);
} else {
$ids = $freetag->get_most_recent_objects();
print "<div class=\"commentsheader\"><b>".$obj['description']."</b>";
print "<br/>";
print $taglist."by ".$obj['wikka_id']." (created: ".$val['tagged_on'].")";
print "</div><div class=\"clear\"> </div>";
$url_opts = $this->href()."&action=list_yours&tag=";
output_tag_cloud($freetag,$url_opts,$tagger_id,"Your tags:");
$ids = "";
if(isset($_REQUEST['tag']) && $_REQUEST['tag'] != "") {
$ids = $freetag->get_most_recent_objects($tagger_id,$_REQUEST['tag']);
} else {
$ids = $freetag->get_most_recent_objects($tagger_id);
print "<div class=\"commentsheader\"><b>".$obj['description']."</b>";
print " <a href=\"".$this->href()."&action=edit&object_id=".$val['object_id']."\">edit</a>";
print "|";
print "<a href=\"".$this->href()."&action=delete&object_id=".$val['object_id']."\">delete</a>";
print "<br/>";
- Save the following file as ##actions/Wikka.freetag.class.php## (needs to be moved to libs/ eventually):
**Wikka.freetag.class.php**
* $Id: Wikka.freetag.class.php,v 1.3 2006/05/29 16:51:23 brian Exp brian $
* Wikka.freetag.class.php
* Extension to Gordon Luk's Freetag for PHP4 (see credits) for
* use with Wikka Bookmark action/handler
* @param string Tag to filter by
function get_most_recent_objects($tagger_id = NULL, $tag = NULL, $offset = 0, $limit = 25) {
$sql = "";
if(!$tag) {
$sql = "SELECT DISTINCT object_id, tagged_on FROM
${prefix}freetagged_objects
WHERE 1
$tagger_sql
ORDER BY tagged_on DESC
LIMIT $offset, $limit ";
$tag = $db->qstr($tag, get_magic_quotes_gpc());
$sql = "SELECT DISTINCT object_id, tagged_on
FROM ${prefix}freetagged_objects INNER JOIN ${prefix}freetags ON (tag_id = id)
WHERE tag = $tag
$tagger_sql
ORDER BY tagged_on DESC
LIMIT $offset, $limit ";
Deletions:
- how about something like my tags / all tags?
- Need to fix logic so "your" tags only display "your" links (also, identify tag cloud as "all" or "yours")
- 1. please put the wikka_freetag class in a file like Wikka.freetag.class.php in the new \libs directory. 2. $freetag_options -> why not use the params provided by wikka ($this->mysql_host, etc.)?
function get_most_recent_objects($tagger_id = NULL, $offset = 0, $limit = 25) {
$sql = "SELECT DISTINCT object_id, tagged_on FROM
${prefix}freetagged_objects
WHERE 1
$tagger_sql
ORDER BY tagged_on DESC
LIMIT $offset, $limit ";
function output_tag_cloud($freetag,$tag_page_url,$tagger_id=NULL) {
print
$freetag->get_tag_cloud_html(100,10,20,'px','cloud_tag',$tag_page_url,$tagger_id);
"<a href=\"".$this->href()."&action=list_yours"."\">List yours</a><br/>";
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="add") && trim($_POST['tags']) != "") {
// Private bookmark?
$private = 0;
if(strpos(trim($_POST['tags']), "@private") !== false)
$private = 1;
$this->Query("insert ".$freetag_options['db_name'].".freetag_bookmarks set ".
"title = '".mysql_real_escape_string($_POST['title'])."', ".
"uri = '".mysql_real_escape_string($this->cleanUrl($_POST['uri']))."', ".
"wikka_id = '".$wikka_id."', ".
"private = '".$private."', ".
"description = '".mysql_real_escape_string($_POST['desc'])."';");
$last_id = $this->LoadSingle("select last_insert_id();");
$freetag->tag_object($tagger_id, $last_id['last_insert_id()'], $_POST['tags']);
$url_opts = $this->href()."&action=get_tag&tag=";
output_tag_cloud($freetag,$url_opts);
$ids = $freetag->get_most_recent_objects();
print "<div class=\"commentsheader\"><b>".$obj['description']."</b><br/>";
$url_opts =
$this->href()."&action=get_tag&tagger_id=".$tagger_id."&tag=";
output_tag_cloud($freetag,$url_opts,$tagger_id);
$ids = $freetag->get_most_recent_objects($tagger_id);
print "<div class=\"commentsheader\"><b>".$obj['description']."</b><br/>";
// List bookmarks with specific tags (ordered by most recent)
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="get_tag")) {
if(isset($_REQUEST['tag'])) {
$valid_id = NULL;
if(isset($_REQUEST['tagger_id']) && ($tagger_id == $_REQUEST['tagger_id'])) {
$valid_id = $tagger_id;
// Output tag cloud
if($valid_id) {
$url_opts = $this->href()."&action=get_tag&tagger_id=".$valid_id."&tag=";
$url_opts = $this->href()."&action=get_tag&tag=";
output_tag_cloud($freetag,$url_opts,$valid_id);
$ids = $freetag->get_objects_with_tag($_REQUEST['tag'],0,100,$valid_id);
foreach($ids as $id) {
$tags = $freetag->get_tags_on_object($id);
$private = '';
if(!$valid_id) {
$private = " AND private = 0";
$objs = $this->LoadAll("select title,uri,description,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$id.$private.";");
foreach($objs as $idx=>$obj) {
print "<a href=\"".$obj['uri']."\">".$obj['title']."</a><br/>";
print "<div class=\"commentsheader\"><b>".$obj['description']."</b><br/>";
$tag_type = "raw_tag";
if(!$valid_id)
$tag_type = "tag";
$taglist .= trim($res[$tag_type])." ";
print $taglist."by ".$obj['wikka_id']." (created: ".$val['tagged_on'].")</div>";
print "<div class=\"clear\"> </div>";
/*
$tagArray = $freetag->get_tag_cloud_tags(100, $user_id);
if (count($tagArray) > 0) {
print "<div class='floatr'><table>";
foreach ( $tagArray as $tag => $qty ) {
print "<tr><td>".$qty."</td><td>".htmlspecialchars($tag)."</td></tr><br/>";
print "</table></div>";
print "<div class=\"clear\"> </div>";
} else {
?>
<div class="title">Be the first to add some
tags!</div>
<?php
}
*/
<?php print($this->FormOpen()); ?>
<input type="hidden" name="action" value="add" />
<table>
<tr>
<td align="right">Title:</td>
<td><input name="title" size="40"/></td>
</tr>
<tr>
<td align="right">URI:</td>
<td><input name="uri" size="40"/></td>
</tr>
<tr>
<td align="right">Description:</td>
<td><input name="desc" size="40"/></td>
</tr>
<tr>
<td align="right"></td>
<td><?php echo $this->Format("Use blank space between tags, include \"@private\" for private bookmark"); ?></td>
</tr>
<tr>
<td align="right">Tags:</td>
<td><input name="tags" size="40"/></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Add" size="40" /></td>
</tr>
</table>
<?php print($this->FormClose()); ?>


Revision [14371]

Edited on 2006-05-27 03:05:13 by BrianKoontz [More TODOs]
Additions:
- Create bookmarklets such as these (borrowed from [[de.lirio.us]]):
Deletions:
- Create bookmarklets such as these (borrowed from [[de.lirio.us]]:


Revision [14370]

Edited on 2006-05-27 03:04:22 by BrianKoontz [More TODOs]
Additions:
- 1. please put the wikka_freetag class in a file like Wikka.freetag.class.php in the new \libs directory. 2. $freetag_options -> why not use the params provided by wikka ($this->mysql_host, etc.)?
- Create bookmarklets such as these (borrowed from [[de.lirio.us]]:
post bookmarklet: javascript:location.href='http://de.lirio.us/rubric/post?uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&when_done=go_back'
post bookmarklet (with popup): javascript:void(open('http://de.lirio.us/rubric/post?uri='+escape(location.href)+'&title='+encodeURIComponent(document.title)+'&when_done=close','Rubric','toolbar=no,width=700,height=325,scrollbars'));


Revision [14368]

Edited on 2006-05-26 12:34:26 by BrianKoontz [More TODOs]
Additions:
- Tag pages as well as links


Revision [14322]

Edited on 2006-05-25 21:12:35 by BrianKoontz [More TODOs]
Additions:
- Paging, user prefs


Revision [14321]

Edited on 2006-05-25 11:19:09 by BrianKoontz [More TODOs]
Additions:
- Need to fix logic so "your" tags only display "your" links (also, identify tag cloud as "all" or "yours")
- Optional logic to display tags in a right-justified vertical list instead of a cloud


Revision [14313]

Edited on 2006-05-23 01:53:10 by BrianKoontz [Posted code]
Additions:
- **This code is most likely not yet safe for a production environment!** There is still some user data validation that needs to be done.


Revision [14312]

Edited on 2006-05-23 01:48:42 by BrianKoontz [Posted code]
Additions:
- Create a MySQL database (I used ##freetag## as the DB name) and use the MySQL dump below to create the necessary tables (such as ##mysql -u root freetag < wikka_freetag.sql##):
Deletions:
- Create a MySQL database (such as ##freetag##) and use the MySQL dump below to create the necessary tables (such as ##mysql -u root freetag < wikka_freetag.sql##):


Revision [14311]

Edited on 2006-05-23 01:47:08 by BrianKoontz [Post code]
Additions:
- Adjust the DB connection parameters for your own installation


Revision [14310]

Edited on 2006-05-23 01:44:20 by BrianKoontz [Posted code]
Additions:
An integrated bookmark manager in the spirit of [[de.lirio.us]].
- Allow tagging of objects other than URIs (such as pages)
- Implement as both a handler and an action
==Installation==
- Install a copy of [[http://www.getluky.net/freetag/ Freetag]] in **3rdparty/plugins/** (I am using v0.240 for testing)
- Create a MySQL database (such as ##freetag##) and use the MySQL dump below to create the necessary tables (such as ##mysql -u root freetag < wikka_freetag.sql##):
**wikka_freetag.sql**
%%(sql)
-- MySQL dump 9.11
-- Freetag Structure v2.02
--
-- Table structure for table `freetags`
--
DROP TABLE IF EXISTS freetags;
CREATE TABLE freetags (
id int(10) unsigned NOT NULL auto_increment,
tag varchar(30) NOT NULL default '',
raw_tag varchar(50) NOT NULL default '',
PRIMARY KEY (id)
) TYPE=MyISAM;
--
-- Table structure for table `freetagged_objects`
--
DROP TABLE IF EXISTS freetagged_objects;
CREATE TABLE freetagged_objects (
tag_id int(10) unsigned NOT NULL default '0',
tagger_id int(10) unsigned NOT NULL default '0',
object_id int(10) unsigned NOT NULL default '0',
tagged_on datetime NOT NULL default '0000-00-00 00:00:00',
PRIMARY KEY (`tag_id`,`tagger_id`,`object_id`),
KEY `tag_id_index` (`tag_id`),
KEY `tagger_id_index` (`tagger_id`),
KEY `object_id_index` (`object_id`)
) TYPE=MyISAM;
--
-- Table structure for table `freetag_bookmarks`
--
DROP TABLE IF EXISTS freetag_bookmarks;
CREATE TABLE freetag_bookmarks (
bookmark_id int(10) unsigned NOT NULL auto_increment,
title varchar(255) NOT NULL default '',
uri varchar(255) NOT NULL,
description varchar(255) default NULL,
wikka_id varchar(75) NOT NULL,
private tinyint(1) NOT NULL default '0',
PRIMARY KEY (`bookmark_id`),
KEY `bookmark_id_index` (`bookmark_id`)
) TYPE=MyISAM;
--
-- Table structure for table `freetag_wikka_id_map`
--
DROP TABLE IF EXISTS freetag_wikka_id_map;
CREATE TABLE freetag_wikka_id_map (
tagger_id int(10) unsigned NOT NULL AUTO_INCREMENT,
wikka_id varchar(75) NOT NULL default '',
PRIMARY KEY(`tagger_id`)
) TYPE=MyISAM;
%%
- Save the following file as ##actions/bookmarks.php##:
**bookmarks.php**
%%(php)
<?php
/*******************************************************************
* Tagged object manager
*
* Allows users (registered or not) to tag objects such as
* bookmarks and display linked tags in the spirit of
* de.licio.us/de.lirio.us.
*
* Usage: {{bookmarks}}
*
* Credits:
* Gordon Luk's Freetag for PHP4 (http://www.getluky.net/freetag)
*
* Author:
* Copyright (c) 2006 Brian Koontz <brian@pongonova.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public Licence for more details:
*
* http://www.gnu.org/copyleft/gpl.html
*
*********************************************************************/
// Check for freetag library
$freetag_lib = '3rdparty/plugins/freetag/freetag.class.php';
if(!is_file($freetag_lib)) {
print("<br/><br/><div class=\"error\">".$this->Format("Can't find $freetag_lib!")."<br/><br/>\n");
die();
}
include_once($freetag_lib);
/********************************************************************/
/* DB connection params */
/********************************************************************/
$freetag_options = array(
'db_user' => 'root',
'db_pass' => '',
'db_host' => 'localhost',
'db_name' => 'freetag',
'PCONNECT' => true,
);
/********************************************************************/
/* Freetag subclass (defines additional support methods) */
/********************************************************************/
class wikka_freetag extends freetag {
function wikka_freetag($freetag_options) {
parent::freetag($freetag_options);
}

/**
* get_most_recent_objects
*
* This function returns the most recent object ids in the
* freetag system, with offset and limit support for
* pagination. It also supports restricting to an individual
* user. Call it with no parameters for a list of 25 most
* recent tags.
*
* @param int The unique ID of the person to restrict results to.
* @param int The offset of the object to start at.
* @param int The number of object ids to return in the result set.
*
* @return array Returns a PHP array with object ids ordered by
* timestamp descending.
* Each element is an associative array with the following elements:
* - 'object_id' => Object id
* - 'tagged_on' => The timestamp of each object id
*/
function get_most_recent_objects($tagger_id = NULL, $offset = 0, $limit = 25) {
$db = $this->db;
if(isset($tagger_id)) {
$tagger_sql = "AND tagger_id = $tagger_id";
} else {
$tagger_sql = "";
}
$prefix = $this->_table_prefix;
$sql = "SELECT DISTINCT object_id, tagged_on FROM
${prefix}freetagged_objects
WHERE 1
$tagger_sql
ORDER BY tagged_on DESC
LIMIT $offset, $limit ";
$rs = $db->Execute($sql) or die("Syntax Error: $sql");
$retarr = array();
while(!$rs->EOF) {
$retarr[] = array(
'object_id' => $rs->fields['object_id'],
'tagged_on' => $rs->fields['tagged_on']
);
$rs->MoveNext();
}
return $retarr;
}
}
/********************************************************************/
/*********************************************************************/
/* Helper functions */
/*********************************************************************/
/* wikka_id_to_tagger_id
*
* Converts Wikka id (returned from GetUserName()) to
* tagger_id. Adds Wikka id to table if not already present.
* Maps NULL -> NULL.
*
*/
function wikka_id_to_tagger_id ($wikka_id, $obj, $freetag_options) {
if(!isset($wikka_id)) {
return NULL;
}
$res = $obj->LoadSingle("select tagger_id from ".$freetag_options['db_name'].".freetag_wikka_id_map where wikka_id = '".$wikka_id."';");
$tagger_id = $res['tagger_id'];
if(!$tagger_id) {
$obj->Query("insert ".$freetag_options['db_name'].".freetag_wikka_id_map set "."wikka_id = '".$wikka_id."';");
$res = $obj->LoadSingle("select last_insert_id();");
$tagger_id = $res['last_insert_id()'];
}
return $tagger_id;
}
/********************************************************************/
/* output_tag_cloud
*
* Create a cloud of tags!
*
* NOTE: Set $tagger_id to NULL for all tags (except those marked
* private)
*/
function output_tag_cloud($freetag,$tag_page_url,$tagger_id=NULL) {
// Output tag cloud
print "<div class='floatr'>";
print
$freetag->get_tag_cloud_html(100,10,20,'px','cloud_tag',$tag_page_url,$tagger_id);
print "</div>";
print "<div class=\"clear\"> </div>";
}
/********************************************************************/
echo "<h1>BookmarkTest</h1>";
echo "<a href=\"".$this->href()."&action=list_all"."\">List all</a> | ".
"<a href=\"".$this->href()."&action=list_yours"."\">List yours</a><br/>";
$wikka_id = $this->GetUserName();
$tagger_id = wikka_id_to_tagger_id($wikka_id, $this, $freetag_options);
$freetag = new wikka_freetag($freetag_options);
// Add new entry to DB
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="add") && trim($_POST['tags']) != "") {
// Private bookmark?
$private = 0;
if(strpos(trim($_POST['tags']), "@private") !== false)
$private = 1;
$this->Query("insert ".$freetag_options['db_name'].".freetag_bookmarks set ".
"title = '".mysql_real_escape_string($_POST['title'])."', ".
"uri = '".mysql_real_escape_string($this->cleanUrl($_POST['uri']))."', ".
"wikka_id = '".$wikka_id."', ".
"private = '".$private."', ".
"description = '".mysql_real_escape_string($_POST['desc'])."';");
$last_id = $this->LoadSingle("select last_insert_id();");
$freetag->tag_object($tagger_id, $last_id['last_insert_id()'], $_POST['tags']);
}
// List all bookmarks (ordered by most recent)
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="list_all")) {
// Output tag cloud
$url_opts = $this->href()."&action=get_tag&tag=";
output_tag_cloud($freetag,$url_opts);
$ids = $freetag->get_most_recent_objects();
foreach($ids as $key=>$val) {
$tags = $freetag->get_tags_on_object($val['object_id']);
$objs = $this->LoadAll("select title,uri,description,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$val['object_id']." and private = 0;");
foreach($objs as $idx=>$obj) {
print "<a href=\"".$obj['uri']."\">".$obj['title']."</a><br/>";
print "<div class=\"commentsheader\"><b>".$obj['description']."</b><br/>";
$taglist = "";
foreach($tags as $idx=>$res) {
$taglist .= trim($res['tag'])." ";
}
print $taglist."by ".$obj['wikka_id']." (created: ".$val['tagged_on'].")</div>";
print "<div class=\"clear\"> </div>";
}
}
}
// List user bookmarks (ordered by most recent)
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="list_yours")) {
// Output tag cloud
$url_opts =
$this->href()."&action=get_tag&tagger_id=".$tagger_id."&tag=";
output_tag_cloud($freetag,$url_opts,$tagger_id);
$ids = $freetag->get_most_recent_objects($tagger_id);
foreach($ids as $key=>$val) {
$tags = $freetag->get_tags_on_object($val['object_id']);
$objs = $this->LoadAll("select title,uri,description,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$val['object_id'].";");
foreach($objs as $idx=>$obj) {
print "<a href=\"".$obj['uri']."\">".$obj['title']."</a><br/>";
print "<div class=\"commentsheader\"><b>".$obj['description']."</b><br/>";
$taglist = "";
foreach($tags as $idx=>$res) {
$taglist .= trim($res['raw_tag'])." ";
}
print $taglist."by ".$obj['wikka_id']." (created: ".$val['tagged_on'].")</div>";
print "<div class=\"clear\"> </div>";
}
}
}
// List bookmarks with specific tags (ordered by most recent)
if(isset($_REQUEST['action']) && ($_REQUEST['action']=="get_tag")) {
if(isset($_REQUEST['tag'])) {
$valid_id = NULL;
if(isset($_REQUEST['tagger_id']) && ($tagger_id == $_REQUEST['tagger_id'])) {
$valid_id = $tagger_id;
}
// Output tag cloud
if($valid_id) {
$url_opts = $this->href()."&action=get_tag&tagger_id=".$valid_id."&tag=";
} else {
$url_opts = $this->href()."&action=get_tag&tag=";
}
output_tag_cloud($freetag,$url_opts,$valid_id);
$ids = $freetag->get_objects_with_tag($_REQUEST['tag'],0,100,$valid_id);
foreach($ids as $id) {
$tags = $freetag->get_tags_on_object($id);
$private = '';
if(!$valid_id) {
$private = " AND private = 0";
}
$objs = $this->LoadAll("select title,uri,description,wikka_id from ".$freetag_options['db_name'].".freetag_bookmarks where bookmark_id = ".$id.$private.";");
foreach($objs as $idx=>$obj) {
print "<a href=\"".$obj['uri']."\">".$obj['title']."</a><br/>";
print "<div class=\"commentsheader\"><b>".$obj['description']."</b><br/>";
$taglist = "";
$tag_type = "raw_tag";
if(!$valid_id)
$tag_type = "tag";
foreach($tags as $idx=>$res) {
$taglist .= trim($res[$tag_type])." ";
}
print $taglist."by ".$obj['wikka_id']." (created: ".$val['tagged_on'].")</div>";
print "<div class=\"clear\"> </div>";
}
}
}
}
/*
$tagArray = $freetag->get_tag_cloud_tags(100, $user_id);
if (count($tagArray) > 0) {
print "<div class='floatr'><table>";
foreach ( $tagArray as $tag => $qty ) {
print "<tr><td>".$qty."</td><td>".htmlspecialchars($tag)."</td></tr><br/>";
}
print "</table></div>";
print "<div class=\"clear\"> </div>";
} else {
?>
<div class="title">Be the first to add some
tags!</div>
<?php
}
*/
?>
<?php print($this->FormOpen()); ?>
<input type="hidden" name="action" value="add" />
<table>
<tr>
<td align="right">Title:</td>
<td><input name="title" size="40"/></td>
</tr>
<tr>
<td align="right">URI:</td>
<td><input name="uri" size="40"/></td>
</tr>
<tr>
<td align="right">Description:</td>
<td><input name="desc" size="40"/></td>
</tr>
<tr>
<td align="right"></td>
<td><?php echo $this->Format("Use blank space between tags, include \"@private\" for private bookmark"); ?></td>
</tr>
<tr>
<td align="right">Tags:</td>
<td><input name="tags" size="40"/></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Add" size="40" /></td>
</tr>
</table>
<?php print($this->FormClose()); ?>
%%
- (Optional) After line 918 in ##freetag.class.php##, add the following line to prevent @private tags from cluttering up the landscape:
%%(php)
foreach ($tag_list as $tag => $qty) {
// Add the following line:
if(strpos($tag, "private") !== false) continue;
$size = $min_font_size + ($qty - $min_qty) * $step;
%%
- Add the ""{{bookmarks}}"" handler code to a new page
Deletions:
I've been toying with the idea of implementing an integrated bookmark manager into Wikka. With the demise of [[de.lirio.us]] imminent, I originally thought it would be most time-efficient to attempt a port of the [[http://search.cpan.org/dist/Rubric Rubric]] engine. Upon further review, I believe Rubric is the poster child of all things ugly with Perl (and this coming from a long-time Perl coder!). I'd like to borrow some features from Rubric and incorporate them into the bookmark manager.
- Allow tagging
- ++I'll post some screenshots of de.lirio.us here for contemplation purposes++
- I've been looking at [[http://www.scuttle.org Scuttle]] (what a great find, thanks to Nils!), and I think I'll work with it instead. It's open source and written in PHP, so it should be much easier to integrate with Wikka than Rubric. I have it up and running, and it seems like these will be the mods necessary to have a truly integrated bookmark manager using Scuttle:
- Create an interface between Scuttle's user auth routines and Wikka's
- Create Wikka templates in place of the Scuttle standalone templates
- Use Wikka's DB connection info rather than a separate Scuttle config
- Implement as a handler or an action? Or a combination of both?
- I have a very rudimentary manager running, using the stock Scuttle code. Lots of ugly hacks, though, in the action code due to the prolific use of global vars in Scuttle (I have to manually identify and set each Scuttle global var in the Wikka $GLOBALS array). Which leads me to a design decision: Should I try to keep as much of Scuttle as intact as possible, relying on hacks and tricks at the Wikka level, or should I try to strip out as much cruft from Scuttle as possible for a truly integrated solution? There's actually an advantage to choice #1: It would be much easier to keep track of Scuttle updates. Choice #2 results in a cleaner implementation, but at the cost of having to manually implement future Scuttle improvements.
- Post action code, modifications to Freetag library


Revision [14295]

Edited on 2006-05-21 20:38:22 by BrianKoontz [Added TODOs]
Additions:
- As a general consideration it would be nice to provide this functionality as a handler.
- it would be nice to have a link for the add-form near list yours.
- how about something like my tags / all tags?
- what would be usefull: If I tag a link with the name of an existing wiki-page, this link could show up at the end of the page.


Revision [14292]

Edited on 2006-05-20 23:32:20 by BrianKoontz [Beta test announcement]
Additions:
- **Beta test announcement:** Well, after hacking around with Scuttle, I decided it was much too complex for what I had in mind. Nils suggested another project, [[http://getluky.net/freetag/ Freetag]], that seems to provide the basic functionality needed without all of the formatting/presentation overhead. I have a beta up and running [[http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks here]], and would appreciate your comments and suggestions! Keep in mind this is still quite rough around the edges, but I was aiming more towards proof-of-concept. I think something like this would be a cool addition (as a 100% modular plugin) to Wikka...
Deletions:
- **Beta test announcement** Well, after hacking around with Scuttle, I decided it was much too complex for what I had in mind. Nils suggested another project, [[http://getluky.net/freetag/ Freetag]], that seems to provide the basic functionality needed without all of the formatting/presentation overhead. I have a beta up and running [[http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks here]], and would appreciate your comments and suggestions! Keep in mind this is still quite rough around the edges, but I was aiming more towards proof-of-concept. I think something like this would be a cool addition (as a plugin) to Wikka...


Revision [14291]

Edited on 2006-05-20 23:31:07 by BrianKoontz [Beta test announcement]
Additions:
- **Beta test announcement** Well, after hacking around with Scuttle, I decided it was much too complex for what I had in mind. Nils suggested another project, [[http://getluky.net/freetag/ Freetag]], that seems to provide the basic functionality needed without all of the formatting/presentation overhead. I have a beta up and running [[http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks here]], and would appreciate your comments and suggestions! Keep in mind this is still quite rough around the edges, but I was aiming more towards proof-of-concept. I think something like this would be a cool addition (as a plugin) to Wikka...
Deletions:
- Well, after hacking around with Scuttle, I decided it was much too complex for what I had in mind. Nils suggested another project, [[http://getluky.net/freetag/ Freetag]], that seems to provide the basic functionality needed without all of the formatting/presentation overhead. I have a beta up and running [[http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks here]], and would appreciate your comments and suggestions! Keep in mind this is still quite rough around the edges, but I was aiming more towards proof-of-concept. I think something like this would be a cool addition (as a plugin) to Wikka...


Revision [14290]

Edited on 2006-05-20 23:29:51 by BrianKoontz [Beta test announcement]
Additions:
- Post action code, modifications to Freetag library


Revision [14289]

Edited on 2006-05-20 23:28:16 by BrianKoontz [Beta test announcement]
Additions:
- Well, after hacking around with Scuttle, I decided it was much too complex for what I had in mind. Nils suggested another project, [[http://getluky.net/freetag/ Freetag]], that seems to provide the basic functionality needed without all of the formatting/presentation overhead. I have a beta up and running [[http://durango.dcccd.edu/wikitest/wikka.php?wakka=Bookmarks here]], and would appreciate your comments and suggestions! Keep in mind this is still quite rough around the edges, but I was aiming more towards proof-of-concept. I think something like this would be a cool addition (as a plugin) to Wikka...
==TODO==


Revision [13720]

Edited on 2006-04-03 08:18:10 by BrianKoontz [Progress report]
Additions:
==Progress reports==
- I have a very rudimentary manager running, using the stock Scuttle code. Lots of ugly hacks, though, in the action code due to the prolific use of global vars in Scuttle (I have to manually identify and set each Scuttle global var in the Wikka $GLOBALS array). Which leads me to a design decision: Should I try to keep as much of Scuttle as intact as possible, relying on hacks and tricks at the Wikka level, or should I try to strip out as much cruft from Scuttle as possible for a truly integrated solution? There's actually an advantage to choice #1: It would be much easier to keep track of Scuttle updates. Choice #2 results in a cleaner implementation, but at the cost of having to manually implement future Scuttle improvements.


Revision [13683]

Edited on 2006-04-02 15:53:16 by BrianKoontz [Added design notes]
Additions:
- ++I'll post some screenshots of de.lirio.us here for contemplation purposes++
- I've been looking at [[http://www.scuttle.org Scuttle]] (what a great find, thanks to Nils!), and I think I'll work with it instead. It's open source and written in PHP, so it should be much easier to integrate with Wikka than Rubric. I have it up and running, and it seems like these will be the mods necessary to have a truly integrated bookmark manager using Scuttle:
- Create an interface between Scuttle's user auth routines and Wikka's
- Create Wikka templates in place of the Scuttle standalone templates
- Use Wikka's DB connection info rather than a separate Scuttle config
- Implement as a handler or an action? Or a combination of both?
Deletions:
- I'll post some screenshots of de.lirio.us here for contemplation purposes


Revision [13661]

Edited on 2006-03-29 12:23:37 by BrianKoontz [Added category]
Additions:
==Category==
CategoryDevelopmentDiscussion


Revision [13655]

The oldest known version of this page was created on 2006-03-29 10:00:59 by BrianKoontz [Added category]
Valid XHTML :: Valid CSS: :: Powered by WikkaWiki