<?xml version="1.0"?>
<rss version="2.0">
  <channel>
    <title>A Wiki</title>
    <link>http://gadgets.xml-comma.org/pub/user/02K_2O9raojF3SwS</link>
    <description><![CDATA[Posts by Douglas Hunter]]></description>
    <language>en-us</language>
    <pubDate>30 Jan 2009 11:22:05 GMT</pubDate>
    <lastBuildDate>30 Jan 2009 11:22:05 GMT</lastBuildDate>
    <docs>http://blogs.law.harvard.edu/tech/rss</docs>
    <generator>Gadgets</generator>
<item>
<title>Putting it all Together</title>
<link>http://gadgets.xml-comma.org/post/main/08ViAS7vVPP54EVX</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[Now that we have the Gadgets framework downloaded and installed, lets get our application up and running.  We'll install the Gadgets Wiki (which relies on ATemplate, APost, APeopleManager and JUtils), and then we'll write a new text-only version of the wiki which uses our new text-only template.

First we build the par files from source that we'll be using:

# from our Gadgets checkout directory
`cd misc/gadgets`
`perl -le 'qx|perl mkpars.pl $_| for ( qw( AEditor APeopleManager APost ATemplate JUtils Wiki ) )'`

`perl ../admin/install_gadget.pl --par AEditor.par --uri_path /admin/aeditor/ --pars_path /ob/webtree/`

`perl ../admin/install_gadget.pl --par APost.par --uri_path /admin/apost/ --pars_path /ob/webtree/`

`perl ../admin/install_gadget.pl --par JUtils.par --uri_path /admin/jutils/ --pars_path /ob/webtree/`

`perl ../admin/install_gadget.pl --par ATemplate.par --uri_path /admin/atemplate/ --pars_path /ob/webtree/`

`perl ../admin/install_gadget.pl --par Wiki.par --uri_path / --pars_path /ob/webtree/`

restart apache to prime the gadget cache

And add an admin user:

`perl ../admin/add_user.pl --email 'dug@allafrica.com' --password 'xxx'`

prints:

"added user GadgetsGroup|main|08VVCyVsfo9OnuZ"

and the special 'admin' group

`perl ../admin/add_group.pl --groupname admin --add_user 'dug@allafrica.com'`

adds our new user to the newly created "admin" group and prints:

"added group GadgetsGroup|main|08VVCyVsfo9OnuZ"

Now we can configure the people admin interface to be only readable by
people in the admin group, securing our installation:

perl ../admin/add_config.pl --gadget "core_auth" --path "/admin/people/" --key "read" --value "GadgetsGroup|main|08VVCyVsfo9OnuZE"

We'll talk more about the configuration API in a bit, but for now
suffice it to say that the Gadgets authentication layer will only
allow members of the admin group we created to read the /admin/people/
uri (or any of its descendants).

Now that we're off and running with a full blown Gadgets wiki stack, let's make some changes.  Historically the Gadgets Wiki web interface has required JavaScript in order to function.  In these new days of hope and change we hope to change that, making it easy for interface writers to layer whichever brand of JS-fu they like on top of the web interface.

Looking at the source of our wiki, for instance the index page `misc/gadgets/wiki-source/mason/index.html`, notice the HTML::Mason inheritance flag set at the bottom of the page:

`
<%flags>
  inherit => '/&TemplateMain/autohandler-main'
</%flags>
`

This tells our wiki to wrap itself using the "autohandler-main" component provided by our "TemplateMain" gadget.  A quick peek at `misc/gadgets/atemplate-source/mason/autohandler-main` reveals that it
simply re-dispatches to `misc/gadgets/atemplate-source/mason/autohandler-postpage`.  We'll start our hacking by creating our own "autohandler-textonly" wrapper that doesn't load any components from the JUtils gadget.

First we turn ATemplate into a development gadget, so we can make our updates directly on the filesystem.

`./misc/admin/create_devel_gadget.pl --name ATemplate --implements ATemplate --implements TemplateMain --implements TemplateEditorPage --implements TemplatePostPage --uri_path /admin/template/devel/ --comp_path template-devel`

followed by a symlink to put the Mason components down our web path:

`ln -s /home/dug/rcs/comma/Gadgets-Oblong/misc/gadgets/atemplate-source/mason/ /ob/webtree/template-devel`

Next we duplicate and modify the wiki sources, like so:

`cp -a misc/gadgets/wiki-source/ misc/gadgets/wiki-textonly-source`

`mv misc/gadgets/wiki-textonly-source/Wiki.devel misc/gadgets/wiki-textonly-source/Wiki-TextOnly.devel`

And in Wiki-TextOnly.devel, s/Wiki/Wiki-TextOnly/g;
s/wiki/wiki-textonly/g; except for the app-string which remains "Wiki"

`./misc/admin/create_devel_gadget.pl --name Wiki-TextOnly --implements Wiki --implements Wiki-TextOnly --uri_path /wiki-to --comp_path wiki-textonly-devel`

`ln -s /home/dug/rcs/comma/Gadgets-Oblong/misc/gadgets/wiki-textonly-source/mason/ /ob/webtree/wiki-textonly-devel`

Now that we've created our new development gadgets, we need to tell the configuration system that for our new `/wiki-to/` URI we want to use our new non-javascript templates that live in `/template-to/`, like so:

`./misc/admin/add_config.pl --gadget ATemplate --path /wiki-to/ --key template_dir --value /template-to/`

And `cp -a /webtree/template/ /webtree/template-to/`.  Now we can start hacking on our newly configured development templates.

Our original "nav" template, which is included by the autohandler that is eventually inherited by our Wiki gadget looks like so:

    <a id="login-link" href="/me/">sign in</a> 
    <script language="javascript" type="text/javascript"><!--
    login_widget ( 'login-link', '/util/login-widget' );
    //--></script>

    <a href="/">home page</a>
    <a id="new-post-link" href="/new-post/">new post</a> 
    <script language="javascript" type="text/javascript"><!--
    new_post_widget ( 'new-post-link', '/util/new-post-widget' );
    //--></script>
    <a href="/pub/stat/">recent activity</a>
    <a href="/pub/tag/">tags</a>
    <a href="/pub/search.html">search</a>

There is some javascript in there, but also the links are static,
which means if we want to relocate our wiki (as we did with our
development wiki, relocating it to /wiki-to/) we need to change the
paths.  We'll fix that by making the links dynamic and eliminating the
javascript, leaving us with our new /webtree/template-to/nav component:

    <a id="login-link" href="<% $g->root %>me/">sign in</a> 
    <a href="<% $g->root %>">home page</a>
    <a id="new-post-link" href="<% $g->root %>new-post/">new post</a> 
    <a href="<% $g->root %>pub/stat/">recent activity</a>
    <a href="<% $g->root %>pub/tag/">tags</a>
    <a href="<% $g->root %>pub/search.html">search</a>`

These nav components are loaded by the autohandler that the wiki inherits from.  Looking at /webtree/wiki-textonly-devel/index.html we see this block:

    <%flags>
      inherit => '/&TemplateMain/autohandler-postpage'
    </%flags>

Our new development ATemplate gadget is implementing the
"TemplateMain" api, and we can see in
/webtree/template-devel/autohandler-postpage that there are javascript
libraries loaded there, and that our template header/footer/nav/css
files are not being served through Mason, but through the send_file
component which sends the content byte for byte.

Let's make a new autohandler-postpage-to template, use it to serve the
newly dynamic nav component:

`cp /webtree/template-devel/autohandler-postpage /webtree/template-devel/autohandler-postpage-to`

And modivy the new text-only authohandler-postpage component to be:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html>
    <head>
    <title><% $title %></title>
    <link rel="stylesheet" type="text/css" href="<% $css_file %>"></link>
    %# <link rel="alternate" type="application/rss+xml" title="<% loc("RSS Feed") %>" href="<% $g->root %>pub/feed/index.xml" />
    <link rel="alternate" type="text/xml" title="<% loc("RSS Feed") %>" href="<% $g->root %>pub/feed/index.xml" />
    </head>
    <div id="all">
    <div id="header"><& $header_file &></div>

    <div id="nav"><& $nav_file &></div>

    <div id="main">
    <% $m->call_next %>
    </div> <!-- end main div -->

    <div id="footer"><& $footer_file &></div>

    </div> <!-- end all div -->
    </body>

    </html>
    %
    %
    <%init>
    my $title = $g->gadget ? $g->gadget->gadget_name : '';
    if ( $m->request_comp->method_exists('title') ) {
      $title .= ' - ' .     $m->request_comp->call_method('title');
    }
    my $template_dir = $g->in_interface->conf_get (     'template_dir' );
    my $header_file = "$template_dir/header";
    my $nav_file = "$template_dir/nav";
    my $footer_file = "$template_dir/footer";
    my $css_file = "$template_dir/base.css";
    </%init>
    %
    %
    %
    <%flags>
      inherit => undef
    </%flags>`

Go ahead and load http://localhost/wiki-to/ in your browser.  You may
see some errors or omissions due to our partially lobotomized
JavaScript.  Don't worry, we'll continue our surgery, eliminating
this needless hunk of coretex.

The first error we encounter is that in a list of posts the "created"
and "latest activity" fields are blank, because we don't load the
"Ago" code anymore.

This is simple to fix.  On the CPAN there is a module called
`Time::Duration` that contains a "ago" method that performs a similar
enough function.  After installing that module, adding a `use
Time::Duration ();` line to /webtree/handler.pl, we can replace the
contents of /webtree/template-devel/util/ago with this:


    <%args>
      $timestamp => undef
    </%args>
    %
    % if ( $timestamp ) {
    <% Time::Duration::ago( time - $timestamp ) %> 
    % }
    %


Refreshing the page should get us back to a proper display.  Moving
down the line by clicking on the "new post" link brings us to the next
problem.  `/webtree/wiki-textonly-devel/index.html` inherits from a
javascripty autohandler.  Changing the "inherit" flag from
`autohandler-editorpage-wiki` to `autohandler-postpage-to` will solve
that particular problem, leaving us with these remaining problems.

What to do with tags, attachments and permissions?

Currently the edit page inside the "APost" gadget has some inlined
JavaScript.  We'll devel-ify this gadget and make a textonly version,
using some delimeted syntax for tags and permissions, and show three
upload entries for new attachments.

Make a devel post gadget:

 ./misc/admin/create_devel_gadget.pl --name APost --implements APost --uri_path /admin/apost/devel/ --comp_path apost-devel

and symlink the components:

ln -s /home/dug/rcs/comma/Gadgets-Oblong/misc/gadgets/apost-source/mason/ /ob/webtree/apost-devel

In /ob/webtree/wiki-textonly-devel/new-post/index.html, change the call from 

`'/&APost/edit'` to `'/&APost/edit-to'`, and then 
`cp /ob/webtree/apost-devel/edit /ob/webtree/apost-devel/edit-to`

so we have a new edit-to textonly component to work on.



]]></description>
<pubDate>30 Jan 2009 11:22:05 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/08ViAS7vVPP54EVX</guid>
</item>
<item>
<title>Gadgets Feature Requests</title>
<link>http://gadgets.xml-comma.org/post/main/08VVPDJdBZYDwoz6</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[- Make the installation process easier

- Provide virtual machine image of basic Gadgets install]]></description>
<pubDate>29 Jan 2009 19:42:06 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/08VVPDJdBZYDwoz6</guid>
</item>
<item>
<title>Gadgets::Standard::Post.pm</title>
<link>http://gadgets.xml-comma.org/post/main/07zQcRb7yOMpJu07</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[#NAME
    Gadgets::Standars::Post - Post creation and management

#METHODS

##my $post = Gadgets::Standard::Post->new ( [parent_key => doc_key] )
      my $reply = Gadgets::Standars::Post->new( parent_key => $parent->doc_key );

    Creates a new Post doc and returns it. Takes optional named paramater
    "parent_key", which is the doc_key of the parent of this post (for
    replies)

##my $post = Gadgets::Standard::Post->get_by_title( "title" )
      my $post = Gadgets::Standars::Post->get_by_title( "Wiki Documentation" );

    Returns most recently created Post titled "title", or undef.

##my $post = Gadgets::Standard::Post->get_by_id ( "doc_id" )
      my $post = Gadgets::Standars::Post->get_by_id( "1234adsf" );

    Returns Post of id "doc_id", or undef.

##my $post = Gadgets::Standard::Post->get_by_key ( "doc_key" )
      my $post = Gadgets::Standars::Post->get_by_key( "Post|main|1234asfd" );

    Returns Post of key "doc_key", or undef.

##my $it = Gadgets::Standard::Post->iterator( %args )
      my $it = Gadgets::Standars::Post->iterator( %iter_args );

    Convenience wrapper for

      XML::Comma::Def->Post
                     ->get_index("main")
                     ->iterator ( %iter_args )

##my @kids = Gadgets::Standard::Post->get_descendant_keys( "doc_key" )
      my @descendants = 
        Gadgets::Standars::Post->get_descendant_keys( $post->doc_key );

    returns a list of descendants of doc_key

##$post->world_readable
      $post->world_readable  and  show();

    returns true if the post is world readable, false otherwise.

##$post->check_authz ( $user, "perm_type" );
    Where "perm_type" is "read", "write" or "post"

      my $can_read = $post->check_authz( $user, "read" );

##$post->top_parent
    my $originator = $self->top_parent;

    returns the top parent doc object, or $self if we have no ancestors.

##$post->top_parent_key
    my $originator = $self->top_parent_key;

    returns the top parent doc_key, or $self->doc_key if we have no
    ancestors.



]]></description>
<pubDate>21 Oct 2008 11:16:44 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/07zQcRb7yOMpJu07</guid>
</item>
<item>
<title>Gadgets::AuthManager.pm</title>
<link>http://gadgets.xml-comma.org/post/main/07zQZyW2nR4Zy6yM</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[#NAME
    Gadgets::AuthManager - Authenticate and authorize. Flexibly.

#METHODS

##$g->auth->check_authn ( [ $extra_login_params ] )
    check_authn functions similarly to force_authn, except that it only
    returns a user object if the user has already logged on, or undef if
    there is no session authentication. It does NOT redirect to a login page
    like force_authn.

##$g->auth->force_authn ( [ $extra_login_params ] )
    Usually, force_authn is called by system core code (admin_acl_authz, for
    example). But you can call it from the init block of a mason component,
    as well, to force authentication for a given resource. Called in this
    way, the login system cannot handle the passing of POST arguments, but
    everything else works. Handling POST args (again, an earlier version of
    AuthManager did so) should be on the todo list.

##$g->auth->login_init
    Login components are often self-submitting, so that login failures can
    be reported and a login page redisplayed. This method is called at the
    beginning of a login component's init block. If this is a login
    submission, and it's successful, a redirect (external redirect for GET,
    internal redirect for POST) will take the user to the page that was
    originally requested. Otherwise a list of values to be filed into
    various login-form elements will be returned.

##$g->auth->logout ( $optional_redirect )
    Call to log a user out. Always redirects (using $m->redirect) after
    performing whatever logout and cleanup is necessary. With no argument,
    redirects to the url in the HTTP Referer header. If passed an argument,
    uses that string as a target url for the redirect.

##$g->auth->new_user
    Call to creates a new user object and underlying data structure.

##$g->auth->new_user_login
    Call to create -- and immediately log in -- a new user.

##$g->auth->authenticate
    Authenticate "in a vacuum," passing login information through to the
    Authner object. This routine would not normally be called directly, but
    it's here in the API for unusual usages.

##$g->auth->refresh_authn
    Call to refresh a user's authentication. This means that any timeout in
    the authn credential is set anew, extending the authn credential's
    validity.

##$g->auth->refresh_changed_authn
    Call to refresh a user's authentication after user information been
    changed in some underlying way. For example, because UserFactory caches
    the display_name string in a client-side cookie, if you change a user's
    display_name without calling this method, the new name will probably not
    "show up" immediately for Mason-side code.

    Here is a common use of refresh_changed_authn:

      $user->set_display_name ( @args_the_def_expects );
      $self->refresh_changed_authn();

      Your profile has been changed. You are logged in
      as: <% $user->display_name %>.


]]></description>
<pubDate>21 Oct 2008 11:13:02 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/07zQZyW2nR4Zy6yM</guid>
</item>
<item>
<title>Gadgets API</title>
<link>http://gadgets.xml-comma.org/post/main/07zQXqP63s22iNR-</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[[[titled_link( "Gadgets.pm" )]]

[[titled_link( "Gadgets::User.pm" )]]

[[titled_link( "Gadgets::Group.pm" )]]

[[titled_link( "Gadgets::AuthManager.pm" )]]

[[titled_link( "Gadgets::Standard::Post.pm" )]]

]]></description>
<pubDate>21 Oct 2008 11:10:46 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/07zQXqP63s22iNR-</guid>
</item>
<item>
<title>Gadgets::Group.pm</title>
<link>http://gadgets.xml-comma.org/post/main/07zQXdaTNtqZWA-G</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[#NAME
    Gadgets::Group - Group creation and management

#METHODS

##my $group = Gadgets::Group->new_group ( group_name => "My Group" )
    Creates a new group of "group_name". Dies if a group of that name
    already exists. Returns the new GadgetsGroup doc object upon success.

##my $group = Gadgets::Group->get_by_name ( "My Group" )
    Returns either a GadgetsGroup document or undef.

##my $group = Gadgets::Group->get_by_id ( group_id )
    Returns either a GadgetsGroup document or undef.

##my $group = Gadgets::Group->get_by_key ( group_key )
    Returns either a GadgetsGroup document or undef.

##my @groups = Gadgets::Group->get_memberships_recursively( user_key | group_key ... )
    takes one or more GadgetsUser or GadgetsGroup doc_keys, returns the
    inclusive list of group memberships. Usually used to get the list of
    groups a user belongs to, like so:

      my @groups = Gadgets::Group->get_memberships_recursively
                                     ( $user->doc_key );


]]></description>
<pubDate>21 Oct 2008 11:10:33 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/07zQXdaTNtqZWA-G</guid>
</item>
<item>
<title>Gadgets::User.pm</title>
<link>http://gadgets.xml-comma.org/post/main/07zQVtEBu9WxfCoB</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[#NAME
    Gadgets::User - User creation and management

#METHODS

##my $user = Gadgets::User->new_user ( email => 'my@email.addr', password => "s33cr3t" )
    Creates and returns a new GadgetsUser document. Ensures email
    uniqueness, throwing an exception upon failure.

##my $user = Gadgets::User->get_by_email( 'my@email.addr' )
    returns GadgetsUser doc on success, undef on failure.

##my $user = Gadgets::User->get_by_key ( user_key )
    Returns either a GadgetsUser document or undef.

##my $user = Gadgets::User->get_by_id ( user_id )
    Returns either a GadgetsUser document or undef.

##$user->belongs_within( group_key ... )
    Returns true if the user belongs within the group(s), false otherwise.


]]></description>
<pubDate>21 Oct 2008 11:08:41 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/07zQVtEBu9WxfCoB</guid>
</item>
<item>
<title>Gadgets.pm</title>
<link>http://gadgets.xml-comma.org/post/main/07zQTXM7S2kG2hnv</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[
#NAME
    Gadgets - Go Go Gadgets!

#DESCRIPTION
    Gadgets is a framework built out of a suite of Perl modules that makes
    gluing HTML::Mason code and XML::Comma easy and fun.

    Gadgets.pm is the entry point for Gadgets, providing the global $g
    object. Contained in $g are objects of several flavors, including an
    Authentication class, a Configuration class, and a Resolver class. See
    the SEE ALSO section below for links to those modules.

#METHODS

##simple_handle_request
    Basic (and usually adequate) method to call from handler.pl to handle
    each Apache request. See APAHCE CONFIGURATION, below.

##implements ( api  => <api_name>, [ path => <request path> ] )
    Look up which installed Gadget or Gadgets "implement" a particular API.
    This method is used by the resolver to find components using the special
    Gadget ampersand call syntax:

      $m->comp ( '/&Test/foo' );
      <& '/&Test/foo' &>

    When passed a path argument, this method returns a single par_file name
    (or undef, if no installed Gadget has declared that it implements the
    requested API). If multiple Gadgets are installed, the conf system is
    checked for an indication of which Gadget is preferred for the supplied
    path. If no relevant conf value is found, the Gadget with the oldest
    install_time value is returned.

    When not passed a path argument, this method returns a list of installed
    Gadgets that implement the API.

    By convention, interface names are in CamelCase, so that Mason calls to
    interface-defined components are easier to read.

    Example conf keys:

      PATH           KEY                 VALUE             GADGET
      /              'FourColorSkin'  'Sunset Skin'      core_impl
      /foo_blog      'FourColorSkin'  'Dark Sky Skin'    core_impl

##auth
    returns the underlying Gadgets::AuthManager object. See
    Gadgets::AuthManager for the "auth" methods.

##conf
    returns the underlying Gadgets::Configuration object. See
    Gadgets::Configuration for the "conf" methods.

#APACHE CONFIGURATION
    Two levels of configuration are required: a few lines in Apache's
    httpd.conf file (or equivalent), and a Mason handler.pl file.

    In httpd.conf:

      PerlModule       Gadgets::ParResolver;
      PerlTransHandler Gadgets::ParResolver::trans_handler

    and something like the following, to have Mason handle part (or all) of
    Apache's location space:

    <Location /> SetHandler perl-script PerlHandler HTML::Mason </Location>
    PerlRequire /web-working/handler.pl

    Here is a complete handler.pl example:

      package HTML::Mason;

      use HTML::Mason;
      use Apache::Constants qw(:common);
      use Apache::Request;

      use strict;

      {
        package HTML::Mason::Commands;
        use vars qw( $g );

        use XML::Comma;
        use Gadgets;

        $g = Gadgets->new
          ( verbose        => 0,
            comp_root      => '/web-working',
            resolver_class => 'Gadgets::Resolver',
            data_dir       => '/usr/local/apache/mason_data',
            error_mode     => 'output', );
      }

      sub handler {
        my ($r) = @_;

        return -1  if  $r->uri() =~ m|^/static/|;
        my $status;

        $status = $HTML::Mason::Commands::g->simple_handle_request ( $r );
        return $status;
      }

      1;

    You may have noticed the "simple_handle_request" method, used in our
    handler sub above. Here is the code for that method, in its entirety:

      sub simple_handle_request {
        my ( $self, $r ) = @_;

        return DECLINED  if  $r->content_type  and
                             $r->content_type =~ m|^httpd|;

        if ( $r->content_type                 and
             $r->content_type  !~  m|^text| ) {
          if ( $r->pnotes('PAR') ) {
            return $self->{resolver}->send_raw_file ( $r );
          } else {
            return DECLINED;
          }
        }

        return $self->{apache_handler}->handle_request ( $r );
      }

    Many handler.pl setups have complex setups to determine whether (and
    how) Mason should serve top-level requests. If you need to integrate the
    Gadgets resolver into such a setup, you'll need to code your own version
    of the logic above.

    It's important to avoid asking the Gadgets resolver to handle "httpd/*"
    content types. Apache uses some heavy wizardry under the covers to make
    requests for directories to eventually turn into requests for index.html
    files. (And along the way pick up missing trailing slashes.) We're not
    going to be able to do this as well as Apache, so we needs to get out of
    its way as much as possible.

    It's also worth noting that Gadgets will often include binary files that
    need to be served without the benefit of Mason componentization. The
    "simple_handle_request" routine assumes that all "text/*" content types
    are fair game for Mason, but that all other content types will be sent
    byte-for-byte to the client. Your rules for this may differ.

#DEMO
    There is a misc/demo directory in the distribution, with a demo Gadget
    par file and gadget spec.




]]></description>
<pubDate>21 Oct 2008 11:06:10 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/07zQTXM7S2kG2hnv</guid>
</item>
<item>
<title>Gadgets HTTP POST API</title>
<link>http://gadgets.xml-comma.org/post/main/07zQDfNFrgi-Ea47</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[# POST API

In subversion, there is a new file lib/Gadgets/Standard/Post/HTTP.pm.  It
implements a mod_perl handler implementing a Gadget Post Service that can be
configured using an Apache Location directive, like so:

    <Location "/post/service/">
      SetHandler perl-script
      PerlHandler Gadgets::Standard::Post::HTTP
    </Location>

The rest of this document assumes that the service is located at
/post/service/.

The service accepts GET requests (to read a post), POST requests (to make a
new post or reply to an existing post), and PUT requests (to edit an existing
post).


##Required Headers

A valid username and password is required for all requests to the service.
They are sent in the X-Gadgets-Post-Username and X-Gadgets-Post-Password
headers, respectively.  If there is an error authenticating, the service
returns a AUTH_REQUIRED status code and sets a message in the 
X-Gadgets-Post-Authenticate-Error header in the response.


##Reading Posts

GET requests need a doc key in the url, like so: 
http://wiki.oblong.net/post/service/Post|main|07NGOqmsS-Yx2OMN.  If the post
is not found, the status code NOT_FOUND is returned.  If the authenticated
user doesn't have permission to view the post, a FORBIDDEN response is
returned.  Assuming that the post exists and the authenticated user has
permission to view the post, the following headers will be included in the
response:

<pre><code>
X-Gadgets-Post-Doc-Key [the doc key]
X-Gadgets-Post-Title [the title]
X-Gadgets-Post-Tags [a comma separated list of the tags]
X-Gadgets-Post-Owner [the doc key of the owner of the post]
X-Gadgets-Post-Perms-Post [a comma separated list of User/Group doc keys]
X-Gadgets-Post-Perms-Write [a comma separated list of User/Group doc keys] 
X-Gadgets-Post-Perms-Read [a comma separated list of User/Group doc keys]
</code></pre>

The body of the response will contain the body of post.


##Creating New Posts

*note* Currently, only the "application/x-www-form-urlencoded" content
type is supported for POST and PUT.  It is the client authors
responsibility to set the content-type header, as well as URI encode
the body of the POST or PUT.

POST requests must not include a doc key, the post directly to the service,
like so:  http://wiki.oblong.net/post/service/.  The following headers may be
included in the request to the service:

<pre><code>
X-Gadgets-Post-Title [the title]
X-Gadgets-Post-Tags [a comma separated list of the tags]
X-Gadgets-Post-Perms-Post [a comma separated list of User/Group doc keys]
X-Gadgets-Post-Perms-Write [a comma separated list of User/Group doc keys] 
X-Gadgets-Post-Perms-Read [a comma separated list of User/Group doc keys]
X-Gadgets-Post-Is-Draft [a true value makes the post a draft]
X-Gadgets-Post-Is-Reply-To [the doc key of the post this is a reply to]
</code></pre>

The body of the request becomes the body of the post that is created by the
request.  The response of the request is same as if a GET was requested to the
post created.


##Editing Existing Posts

PUT requests need a doc key in the url, like so: 
http://wiki.oblong.net/post/service/Post|main|07NGOqmsS-Yx2OMN.  If the post
is not found, the status code NOT_FOUND is returned.  If the authenticated
user doesn't have permission to view the post, a FORBIDDEN response is
returned.  

The following headers may be included in the request to the service:


<pre><code>
X-Gadgets-Post-Title [the title]
X-Gadgets-Post-Tags [a comma separated list of the tags]
X-Gadgets-Post-Perms-Post [a comma separated list of User/Group doc keys]
X-Gadgets-Post-Perms-Write [a comma separated list of User/Group doc keys] 
X-Gadgets-Post-Perms-Read [a comma separated list of User/Group doc keys]
</code></pre>

The body of the request becomes the body of the post that is being edited by 
the request.  The response of the request is same as if a GET was requested to 
the post edited.


##Headers Reference

<pre><code>
X-Gadgets-Post-Username
X-Gadgets-Post-Password
X-Gadgets-Post-Authenticate-Error

X-Gadgets-Post-Is-Reply-To
X-Gadgets-Post-Title
X-Gadgets-Post-Tags

X-Gadgets-Post-Perms-Post
X-Gadgets-Post-Perms-Write
X-Gadgets-Post-Perms-Read
X-Gadgets-Post-Perms-Error

X-Gadgets-Post-Is-Draft
X-Gadgets-Post-No-Store

X-Gadgets-Post-Doc-Key
X-Gadgets-Post-Owner
</code></pre>

<pre><code>
</code></pre>

Attached is a Perl script that exercises the POST API.]]></description>
<pubDate>21 Oct 2008 10:49:15 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/07zQDfNFrgi-Ea47</guid>
<enclosure url="http://gadgets.xml-comma.org/file/main/07zQDfNFrgi-Ea47/e7PGX5wm" length="2050" type="text/x-perl" />

</item>
<item>
<title>New Gadget Tutorial</title>
<link>http://gadgets.xml-comma.org/post/main/07zPdzUR8L3BfCYW</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[## Writing a new gadget

As mentioned before, a Gadgets installation is made up of one or more
applications bundled as Par files.  Sometimes we refer to an
individual application as a gadget.  A Gadgets installation typically
employes several of these (commonly the Wiki or Blog for high-level
functionality, Post for the actual document creation/edit interface,
and Template for skinning the application).

Once we're up and running, to make a new gadget we change into our
Gadgets checkout directory and run
`./misc/admin/create_devel_gadget.pl`.

And here is the output of create_devel_gadget.pl --help

<pre><code>
  ./misc/admin/create_devel_gadget.pl --name "Gadget-Name"
     --implements "APIName" [ --implements "OtherAPIName" ]
     --uri_path "/uri/to/dispatch/"
     --comp_path "/path/to/mason/components/"
     [ --lib "/path/to/perl/lib/" --lib "/other/path/to/perl/lib/" ]
     [ --def "/path/to/comma/def" --def "/other/path/to/comma/def" ]

  OPTIONS:

    --name

      The name of the gadget you are creating.  This is required, and
      should be representative of the functionality the gadget
      provides.

    --implements

      The name(s) of the API(s) that this gadget implements.  If
      nothing is specified, it will default to the name of the gadget.
      Multiple APIs may be specified, if your gadget implements them.

    --uri_path

      The uri that will dispatch requests to this gadget.  This is
      required, and should be unique to this gadget.

    --comp_path

      The path to your development HTML::Mason components.  This is
      required, and the directory specified must be relative to the
      HTML::Mason component_root specified in your handler.pl (or
      otherwise specified in your Apache configuration).  For
      instance, if your component_root is /mycorp/webtree/, and your
      development components are in the directory
      /mycorp/webtree/kung-fu/, your would specify "/kung-fu/" as your
      comp_path.

    --lib

      The path(s) to a directory(s) containing any perl modules that will be
      bundled with this gadget.  This is typically used for bundling
      gadget specific logic that doesn't belong in the Gadgets
      standard library.

   --def

     The path(s) any XML::Comma document defition(s) that this gadget
     will use.  This is typically used for bundling a gadget specific
     doctype that doesn't belong in the Gadgets standard library.
</code></pre>

Lets start by making a toy application called that lets folks write
book reviews and then have discussions about those reviews.

We'll name this application "Jonas", after Jonas Wright.  Jonas was
flayed alive on August 4, 1632.  His skin was then used to re-bind
what was once his most favored posession, a book.

first, our project directory:

`mkdir ~/prj/gadgets/Jonas`

Then our "comma" directory.  This is where Comma Document Definitions
belong.

`mkdir ~/prj/gadgets/Jonas/comma`

"lib" for our Perl libraries:

`mkdir ~/prj/gadgets/Jonas/lib`

And finally "mason" for our HTML::Mason templates.

`mkdir ~/prj/gadgets/Jonas/mason`

And now we'll make a symlink of our project's HTML::Mason template
directory into our component_root, at the path that I'll use later for
my argument to --uri_path.  Since I keep my component root down
"/webtree":

`ln -s ~/prj/gadgets/Jonas/mason /webtree/jonas`

And make a stub for the Perl module used by Jonas by pasting these contents:

<pre><code>
package Jonas;

use warnings;
use strict;

1;
</code></pre>

to ~/prj/gadgets/Jonas/lib/Jonas.pm.  This is where we'll
refactor commonly used code from our Mason templates into.

Our application will need to store some meta-data about books being
reviewed, so we'll also write a simple Comma Document Definition to
define that metadata, and how it is stored by pasting these contents:

    <DocumentDefinition>
    <name>Review</name>

    <element><name>post_key</name></element>

    <element><name>title</name></element>
    <element><name>ISBN</name></element>
    <element><name>author</name></element>
    <element><name>publisher</name></element>
     
    <element>
      <name>rating</name>
      <macro>enum: 1 .. 5</macro>
    </element>
    
    <required> qw( post_key title ISBN author publisher rating ) </required>
  
    <store>
      <name>main</name>
      <base>review</base>
      <location>GMT_3layer_dir</location>    
      <location>Sequential_file:'extension','.review'</location>
      <file_permissions>0664</file_permissions>
      <index_on_store>main</index_on_store>    
    </store>

    <index>
      <name>main</name>
      <field><name>post_key</name></field>
      <field><name>title</name></field>
      <field><name>ISBN</name></field>
      <field><name>author</name></field>
      <field><name>publisher</name></field>
      <field><name>rating</name></field>
    </index>

    </DocumentDefinition>

to ~/prj/gadgets/Jonas/defs/Review.def


 Now that we have everything stubbed out, we run create_devel_gadget.pl with the following arguments:

<pre><code>
./misc/admin/create_devel_gadget.pl --name Jonas \
                                      --uri_path "/review/" \
                                      --comp_path "/jonas/" \
                                      --lib /home/username/prj/gadgets/Jonas/lib/ \
                                      --def /home/dug/username/gadgets/Jonas/defs/Review.def
</pre></code>

 If everything has gone well so far, we should be able to `echo "hello
 world!" >> /ob/webtree/jonas/test.html`, load up
 http://localhost/review/ in a browser, and see our handiwork!
 (test.html can be deleted after verifying things are working).

 We're going to keep this application very simple.  There will be a
 form to create new reviews, and a page to list existing reviews.

 Let's hack in these basic features.  We'll start with our navigation
 and url dispatch.  By creating `/ob/webtree/jonas/autohandler` that
 looks like this:


    <%init>
      my $base = $g->interface_path( api => "Jonas" );
    </%init>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html>
    <head>
    <title>Jonas</title>
    </head>

    <div class="nav">
      <a href="<% $base . "new" %>">Write a Review</a> | <a href="<% $base . "list" %>">Read Reviews</a> </div>

    % $m->call_next( %ARGS );

    </html>


we add our html declaration and basic navigation to every page.  Autohandler is a special component name in mason that says "execute me every request down this path".

Notice that <%init> block.  We asked the gadgets API for the
interface_path for "Jonas".  That coresponds to the "--uri-path"
argument we passed to create_devel_gadget.pl, and by using this path
we make our application relocatable.

And now a basic `index.html` page:

    <div class="welcome">
    Welcome to Jonas, the book review gadget.  Why is this site called <a href="http://www.google.com/search?hl=en&q=%22Jonas+Wright%22+book+binding">Jonas</a>?
    </div>

which automatically gets wrapped up by our autohandler (like all other
components).  This is the only component that isn't wrapped up by our
URI dispatch component, the dhandler (show next).  Calls to
"http://localhost/review/" or "http://localhost/review/index.html"
skip the dhandler (which only handles virtual requests) and directly
call "index.html".

Next, we'll write a `dhandler`.  Dhandlers in Mason handle virtual
requests, and are a nice and easy way to do URI dispatching.  We set up our URI dispatch by adding making a `/ob/webtree/jonas/dhandler` like so:

    <%doc>
      /new     -> edit
      /edit/id -> edit?doc_id=id
      /read    -> list
      /read/id -> list?doc_id=id
    </%doc>
    %
    <%once>
      use Jonas;
    </%once>
    %
    <%perl>

      my $path = $m->dhandler_arg;

      if ( $path eq "new" ) {
        $m->comp( "edit" );
      } elsif ( $path =~ /^edit\/(.+)/ ) {
        my $id = $1;
        my $review = Jonas->get_review_by_id( $id );
        $review  or  $m->clear_and_abort( 404 );
        $m->comp( "edit", review_doc => $review );
      } elsif ( $path eq "read" ) {
        $m->comp( "list" );
      } elsif ( $path =~ /^read\/(.+)/ ) {
        my $id = $1;
        my $review = Jonas->get_review_by_id( $id );
        $review  or  $m->clear_and_abort( 404 );
        $m->comp( "list", review_doc => $review );
      } else {
        $m->clear_and_abort( 404 );
      }

    </%perl>


We're using the as of yet unwritten `edit` and `list` components here
to handle requests to /review/new, /review/read, etc.

Notice that we're also using the `get_review_by_id` method in our
Jonas.pm library.  Let's add that into
`/home/username/prj/Jonas/lib/Jonas.pm`.  It looks like so:

<pre><code>
sub get_review_by_id {
  my ( $class, $id ) = @_;
  return  unless  $id;
  my $key = XML::Comma::Storage::Util->concat_key
              ( type => "Review", store => "main", id => $id );
  my $review = eval { XML::Comma::Doc->read( $key ) };
  return $review;
}
</pre></code>


And now `/ob/webtree/jonas/edit`:


    <%args>
      $title     => undef
      $ISBN      => undef
      $publisher => undef
      $author    => undef
      $rating    => undef
      $review    => undef
      %errors    => undef

      $review_doc => undef # for edits
    </%args>
    %
    %
    <%init>
      my $user = $g->auth->force_authn;

      my $post_doc;
      if ( $review_doc ) {
        $post_doc   = XML::Comma::Doc->read( $review_doc->post_key );
        $post_doc->check_authz( $user, 'write' )  or  $m->clear_and_abort( 403 );
      }

    </%init>

    <h3>New Book Review</h3>
    <p>All fields are required.</p>
    %if ( %errors ) {
      <div class="new-review-form-errors">
      <% join "<br />", values %errors %>
      </div>
    % }
    <form method="post" action="<% $g->interface_path( api => "Jonas" ) %>process">
    % if ( $review_doc ) {
      <input type="hidden" name="review_key" value="<% $review_doc->doc_key %>" />
    % }
      <table class="new-review-form">
        <tr>
          <td>Title:</td>
          <td>
            <input type="text" name="title" value="<% $title %>" />
          </td>
        </tr>
        <tr>
          <td>ISBN: </td>
          <td>
            <input type="text" name="ISBN" value="<% $ISBN %>" />
          </td>
        </tr>
        <tr>
          <td>Publisher: </td>
          <td>
            <input type="text" name="publisher" value="<% $publisher %>" />
          </td>
        </tr>
        <tr>
          <td>Author: </td>
          <td>
            <input type="text" name="author" value="<% $author %>" />
          </td>
        </tr>
        <tr>
          <td>Rating: </td>
          <td>
            <select name="rating">
    % foreach my $n ( 1 .. 5 ) {
    %   my $selected = ($rating and $rating == $n)  ?  'selected="1"'  : undef;
            <option value="<% $n %>" <% $selected %>><% $n %></option>
    % }
            </select>
          </td>
        </tr>
        <tr>
          <td>Review: </td>
          <td><textarea rows="15" cols="40"
                        name="review"><% $review %></textarea>
        </tr>
        <tr>
          <td>Submit: </td>
          <td><input type="submit" name="submit" value="submit" /></td>
        </tr>
      </table>
    </form>


This is just a form that posts to `process`.  The most interesting
line is probably `$g->auth->force_authn`, which uses our global `$g`
object, which is our entry point to the Gadgets API.  The `auth`
object is documented at `perldoc Gadgets::AuthManager`, and tells us
that `force_authn` forces authentication in order to access the
resource it is called in.  In other words, folks have to be logged in
in order to write a review.

Another interesting couple of lines are:

<pre><code>
  if ( $review_doc ) {
    $post_doc   = XML::Comma::Doc->read( $review_doc->post_key );
    $post_doc->check_authz( $user, 'write' )  or  $m->clear_and_abort( 403 );
  }
</pre></code>

In a minute we'll see that if the POST to `process` is successful, it
creates a `Gadgets::Standard::Post` doc that contains the review
itself, along with a `Review` doc that contains a pointer to the
`Post`, as well as the meta-data we need to collect to make a `Post` a
`Review`.

If someone is trying to edit a review, we not only make sure that they
are logged in, but that they are authorized to write to that
post/review doc pair.  By default the check_authz will return true for
the owner of the doc, which is what we want.

And now for `/ob/webtree/jonas/process`:


    <%args>
      $title     => undef
      $ISBN      => undef
      $publisher => undef
      $author    => undef
      $rating    => undef
      $review    => undef

      $review_key => undef
      %errors     => ()
    </%args>

    <%init>
      my $user = $g->auth->force_authn;

      my ( $review_doc, $post_doc );
      if ( $review_key ) {
        $review_doc = eval { XML::Comma::Doc->read( $review_key ) };
        $post_doc   = XML::Comma::Doc->read( $review_doc->post_key );
        $m->clear_and_abort( 404 )  if  $@;
        $post_doc->check_authz( $user, 'write' )  or  $m->clear_and_abort( 403 );
      }

      $title      or  $errors{ "title"     } = "Title is required.";
      $ISBN       or  $errors{ "ISBN"      } = "ISNB is required.";
      $publisher  or  $errors{ "publisher" } = "publisher is required.";
      $author     or  $errors{ "author"    } = "author is required.";
      $review     or  $errors{ "review" } = "review is required.";
      $rating =~ /^[1-5]$/  or  $errors{ "rating" } = "Rating must be 1 - 5.";

      if ( values %errors ) {
        $ARGS{ "review_doc" } = $review_doc  if  $review_doc;
        $m->comp( 'edit', %ARGS, errors => \%errors );
        return;
      }


      $review_doc ||= XML::Comma::Doc->new( type => "Review" );
      $post_doc   ||= XML::Comma::Doc->new( type => "Post"   );

      eval {
        # first make our post
        $post_doc->owner( $user->doc_key );
        $post_doc->app( "Jonas" );
        $post_doc->body->html( $review );
        $post_doc->store( store => "main" );

        # and then our review
        $review_doc->post_key( $post_doc->doc_key );
        $review_doc->title( $title );
        $review_doc->ISBN( $ISBN );
        $review_doc->publisher( $publisher );
        $review_doc->author( $author );
        $review_doc->rating( $rating );
        $review_doc->store( store => "main" );
      };
      if ( $@ ) {
        $errors{ "store_error" } = $@;
        $m->comp( "edit", %ARGS, errors => \%errors );
        return;
      } else {
        $m->redirect( "read/" . $review_doc->doc_id );
      }
    </%init>


And for completenes, the `list` component:


    <%args>
      $review_doc => undef
      $order_by   => "record_last_modified DESC"
    </%args>

    <%perl>

      if ( $review_doc ) {
        $m->comp( "listing", key => $review_doc->doc_key );
      } else {
        my $iterator = XML::Comma::Def->Review
                                      ->get_index( "main" )
                                      ->iterator( order_by => $order_by );
        while ( ++$iterator ) {
          $m->comp( "listing", key => $iterator->doc_key );
        }
      }

    </%perl>


    <%def listing>
    <%args>
      $key
    </%args>
    <%init>
      my $review = XML::Comma::Doc->read( $key );
      my $post   = XML::Comma::Doc->read( $review->post_key );
    </%init>

    <% $post->body->html %>
    <hr />
    </%def>


And that's it, we now have a working Jonas application, that can be packaged up into a PAR file and deployed live!

To package it up: ` perl misc/admin/package_devel_gadget.pl --name Jonas --comp_root /ob/webtree/`.

(and the output of `package_devel_gadget.pl --help`:

<pre><code>
  misc/admin/package_devel_gadget.pl --name "Gadget-Name"
     --comp_path "/path/to/mason/components/"

  OPTIONS:

    --name GadgetName

      The name of the gadget you are packaging.  This is required, and
      must be a devel gadget (probably created with
      create_devel_gadget.pl --name).

    --comp_root /mason/comp_root/

      This is the HTML::Mason component_root specified in your
      handler.pl (or otherwise specified in your Apache
      configuration).  This is necissary for the script to find your
      Mason components (and is a required argument), which were
      specified relative to this path with the --comp_path option to
      create_devel_gadget.pl.

    --outfile /path/to/GadgetName.par

      Optionally tell package_devel_gadget.pl where to write out your
      new par file.  This defaults to a par file of the argument you
      provided --name (GadgetName.par) in your present working
      directory.
</code></pre>


And install the production gadget: `perl misc/admin/install_gadget.pl  --par Jonas.par`.

(and the output of `install_gadget.pl --help`:)

<pre><code>
  misc/admin/install_gadget.pl --par "Gadget-Name.par"
     [--uri_path "/uri/to/dispatch/" ]
     [ --par_dir "/prod/pars/dir/" ]

  OPTIONS:

    --par

      The par that we're installing, probably generated by
      package_devel_gadget.pl.  This is required.

    --uri_path

      The uri that will dispatch requests to this gadget.  If
      unspecified the script will try to find a devel gadget of the
      same name and use its path.  It is requred when installing a
      production gadget on a system where the devel gadget doesn't
      exist.

    --pars_path

      The path to the directory where your production pars are
      installed.  This is typically somewhere down apache's
      "document_root", and needs to be readable by an apache child.
      The script will try to figure out your pars_path by looking at
      other non-devel installed gadgets, but will die if it can't
      figure out the path and its unspecified.  So it's kind of
      optional, at least after its been specified once.
</code></pre>

The source to Jonas lives in [https://chronicle.allafrica.com:8080/repository/branches/Gadgets-Oblong/misc/demo/](https://chronicle.allafrica.com:8080/repository/branches/Gadgets-Oblong/misc/demo)
]]></description>
<pubDate>21 Oct 2008 10:10:07 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/07zPdzUR8L3BfCYW</guid>
</item>
<item>
<title>Gadgets Installation</title>
<link>http://gadgets.xml-comma.org/post/main/07zPQvpDWi16TSMl</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[
`mkdir -p /ob/gadgets/build/`

`cd /ob/gadgets/build/`

`perl -MCPAN -e shell; 
  install LWP::UserAgent
  quit`


# Apache/mod-perl

`wget http://www.modssl.org/source/mod_ssl-2.8.31-1.3.41.tar.gz`
`wget http://perl.apache.org/dist/mod_perl-1.0-current.tar.gz`
`wget http://www.poolsaboveground.com/apache/httpd/apache_1.3.41.tar.gz`

UNDER MAC OS X also execute because linking against the system libraries is broken:

`wget http://www.openssl.org/source/openssl-0.9.8g.tar.gz`
`tar xvfz openssl-0.9.8g.tar.gz`
`cd openssl-0.9.8g`
`./config`
`make`

END MAC OS X HUNK

`tar -zxvf apache_1.3.41.tar.gz`
`tar -zxvf mod_perl-1.0-current.tar.gz`
`tar -zxvf mod_ssl-2.8.31-1.3.41.tar.gz`

`cd /ob/gadgets/build/mod_ssl-2.8.31-1.3.41`
`./configure --with-apache=../apache_1.3.41` # MAC OS X: `./configure --with-apache=../apache_1.3.41 --with-ssl=../openssl-0.9.8g`
`cd /ob/gadgets/build/mod_perl-1.30/`

`perl Makefile.PL USE_APACI=1 EVERYTHING=1 SSL_BASE=SYSTEM \ 
APACHE_PREFIX=/ob/gadgets/apache-gadgets/ \ 
APACI_ARGS=--enable-module=ssl,--enable-module=rewrite`

MAC OS X HUNK

`perl Makefile.PL USE_APACI=1 EVERYTHING=1 SSL_BASE=/path/to/downloaded/and/configured/openssl \
APACHE_PREFIX=/ob/gadgets/apache-gadgets/ \
APACI_ARGS=--enable-module=ssl,--enable-module=rewrite`

END MAC OS X HUNK

`make test`
`make install`

inside the Apache::Test install above, path to httpd => /ob/gadgets/apache-gadgets/bin/httpd


# Comma

`mkdir /ob/gadgets/comma`

`/usr/sbin/usermod -a -G apache USERNAME
re-login`

`perl -MCPAN -e shell;
  install Class::ClassDecorator Clone Crypt::Blowfish Crypt::CBC DBI Digest::HMAC_MD5 Inline Lingua::Stem Math::BaseCalc PAR Proc::Exists String::CRC
  get DBD::mysql
  quit`

You may need to do a: sudo yum install mysql-devel.x86_64 if you don't already have the mysql dev files

`cd /root/.cpan/build/DBD-mysql-4.006` # or wherever your CPAN is configured 
                                     # to store build files
`perl Makefile.PL
make test;
make install;`

`cd ..` (back to root of build directory)

`svn co https://chronicle.allafrica.com:8080/repository/trunk/XML-Comma /ob/gadgets/build/XML-Comma`

`cd /ob/gadgets/build/XML-Comma`
`perl Makefile.PL`

`make`

`make test # t/indexing....................ok 1/0grep: 
          # .test/usr/local/comma/log.comma: No such file or directory
          # may fail, that's okay`
`make install`

`yum install gd-devel.x86_64`

`perl -MCPAN -e shell;
force install HTML::FromText
install Apache::DBI Class::Container Convert::UU GD GD::Image::Thumbnail HTML::Mason::Plugin HTML::TreeBuilder i18n JSON Lingua::EN::Numbers MIME::Lite Crypt::Twofish Email::Address`
quit`


# Gadgets

`svn co https://chronicle.allafrica.com:8080/repository/branches/Gadgets-Oblong/ /ob/gadgets/build/Gadgets`

`cd /ob/gadgets/build/Gadgets`
`perl Makefile.pl`
`make`
`make install`

# Qmail

`mkdir /ob/gadgets/mail/`
`mkdir /ob/gadgets/mail/qmail`
`mkdir /ob/gadgets/mail/ucspi-tcp`

`mkdir /ob/gadgets/mail/daemontools`

`mkdir /ob/gadgets/build/qmail`
`cd /ob/gadgets/build/qmail`
`wget http://www.qmail.org/netqmail-1.06.tar.gz`
`wget http://cr.yp.to/ucspi-tcp/ucspi-tcp-0.88.tar.gz`
`wget http://cr.yp.to/daemontools/daemontools-0.76.tar.gz`

`tar -zxvf netqmail-1.06.tar.gz`
`tar -zxvf ucspi-tcp-0.88.tar.gz`

`cd /ob/gadgets/build/qmail/netqmail-1.06`
`head -15 INSTALL.ids | grep "#" | perl -lne 's|^\s+#\s+(.*)$|/usr/sbin/$1|; s|/var/|/ob/gadgets/mail/|; print;' > IDS`
`chmod +x IDS`
`./IDS`

`perl -i -p -e 's|/var/qmail|/ob/gadgets/mail/qmail|' conf-qmail`

`make setup check`

`./config-fast gadgets.yourdomain.com`

`cd /ob/gadgets/build/qmail/ucspi-tcp-0.88`
`patch < /ob/gadgets/build/qmail/netqmail-1.06/other-patches/ucspi-tcp-0.88.errno.patch`
`perl -i -p -e 's|/usr/local|/ob/gadgets/mail/ucspi-tcp|' conf-home`

`make`
`make setup check`

NOTE: Now we're going to install the damontools package.  It's not 
easily relocatable, as it needs to write to the inittab during the 
installation, so we'll simply follow the instructions in section 2.7
here http://lifewithqmail.org/lwq.html#installation instead of 
mucking about

`mkdir /package`
`cd /package`
`/ob/gadgets/build/qmail/daemontools-0.76.tar.gz .`
`tar -zxvf daemontools-0.76.tar.gz`
`cd /package/admin/daemontools-0.76/src/`
`patch < /ob/gadgets/build/qmail/netqmail-1.06/other-patches/daemontools-0.76.errno.patch`
`cd /package/admin/daemontools-0.76/`
`package/install`

end damontools installation

<pre><code>
echo "./Maildir/" > /ob/gadgets/mail/qmail/control/defaultdelivery
cp /ob/gadgets/build/qmail/setupfiles/rc /ob/gadgets/mail/qmail/

cp /ob/gadgets/build/qmail/setupfiles/qmailctl /ob/gadgets/mail/qmail/bin/
mkdir -p /ob/gadgets/mail/qmail/supervise/qmail-send/log
mkdir -p /ob/gadgets/mail/qmail/supervise/qmail-smtpd/log

cp /ob/gadgets/build/qmail/setupfiles/qmail-send-run /ob/gadgets/mail/qmail/supervise/qmail-send/run
cp /ob/gadgets/build/qmail/setupfiles/qmail-send-log-run /ob/gadgets/mail/qmail/supervise/qmail-send/log/run

cp /ob/gadgets/build/qmail/setupfiles/qmail-smtpd-run /ob/gadgets/mail/qmail/supervise/qmail-smtpd/run
cp /ob/gadgets/build/qmail/setupfiles/qmail-smtpd-log-run /ob/gadgets/mail/qmail/supervise/qmail-smtpd/log/run

chmod 755 /ob/gadgets/mail/qmail/supervise/qmail-send/run
chmod 755 /ob/gadgets/mail/qmail/supervise/qmail-send/log/run
chmod 755 /ob/gadgets/mail/qmail/supervise/qmail-smtpd/run
chmod 755 /ob/gadgets/mail/qmail/supervise/qmail-smtpd/log/run

mkdir -p /ob/gadgets/mail/log/qmail/smtpd
chown qmaill /ob/gadgets/mail/log/qmail /ob/gadgets/mail/log/qmail/smtpd

echo 20 > /ob/gadgets/mail/qmail/control/concurrencyincoming
echo '127.:allow,RELAYCLIENT=""' >>/ob/gadgets/mail/ucspi-tcp/tcp.smtp
/ob/gadgets/mail/qmail/bin/qmailctl cdb

/sbin/chkconfig sendmail off

cp /ob/gadgets/build/Gadgets/misc/bin/email_wiki_posts_from_cron.pl /ob/gadgets/mail/qmail/bin/
cp /ob/gadgets/build/Gadgets/misc/bin/process_email_relay.pl /ob/gadgets/mail/qmail/bin/
cp /ob/gadgets/build/Gadgets/misc/bin/process_email_wikipost.pl /ob/gadgets/mail/qmail/bin/
cp /ob/gadgets/build/Gadgets/misc/bin/process_email_wikireply.pl /ob/gadgets/mail/qmail/bin/

chown root:qmail /ob/gadgets/mail/qmail/bin/*.pl

cp /ob/gadgets/build/qmail/setupfiles/qmail* /ob/gadgets/mail/qmail/alias/

chown -R apache:nofiles /ob/gadgets/comma/
chmod -R 664 /ob/gadgets/comma/

ln -s /ob/gadgets/mail/qmail/supervise/qmail-send /service
ln -s /ob/gadgets/mail/qmail/supervise/qmail-smtpd /service
</code></pre>

]]></description>
<pubDate>21 Oct 2008 09:55:07 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/07zPQvpDWi16TSMl</guid>
</item>
<item>
<title>Gadgets Documentation Index</title>
<link>http://gadgets.xml-comma.org/post/main/07zPOsC-Hian0HW3</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[[[titled_link( "Gadgets Installation" )]]

[[titled_link( "New Gadget Tutorial" )]]

[[titled_link( "Gadgets API" )]]

[[titled_link( "Putting it all Together" )]]

[[titled_link( "Gadgets HTTP POST API" )]]

[[titled_link( "Gadgets Feature Requests" )]]


]]></description>
<pubDate>21 Oct 2008 09:52:56 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/07zPOsC-Hian0HW3</guid>
</item>
<item>
<title>Gadgets TODO</title>
<link>http://gadgets.xml-comma.org/post/main/03eJwJWUgTm0Rdlc</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[<p>NOTE:</p><ul><li>grumble grumble install process grumble grumble.</li><ul><li>installation process:</li><ul><li>1 - add a user, and bless them into the administrators group</li></ul><ul><li>2 - run these commands:</li><ul><li>sudo /usr/local/src/gadgets/misc/admin/add_config.pl --gadget core_auth --path /me/ --key read --value all</li><li>sudo /usr/local/src/gadgets/misc/admin/add_config.pl --gadget core_auth --path /admin/ --key read --value #get admin group doc_key here</li><li>sudo /usr/local/src/gadgets/misc/admin/add_config.pl --gadget core_auth --path / --key admin --value #get admin group doc_key here <br /></li></ul></ul></ul></ul><p>BUGS:</p><ul><li>handler IP/netmask addresses cleaner, allow support for skipping login check<br /></li><li>allow admin to set a site-wide default permission config<br /></li><li>do translation the Right Way<br /></li><li>don't give a 500 internal saerver error when you can't find a par file, give something more friendly - nuke ARegistration.par and click on register for example</li><li>Get rid of this stupidity: &quot;Could not erase this post, error was: actually erasing ok.&quot;<br /></li><li>don't give a 403 error for mysql server going away (wtf?)<br /></li><li>further work on the docs</li><li>configure order of posts on a per-list page basis (home page and blog are the two current modes - should we introduce the ability o extend this arbitrarily many?).<br /></li><li>/util/erased should redirect to parent if we just deleted a reply<br /></li><li>health and travel wikis are occasionally fubar'ing with user not exist errors again... wtf?!? a couple of things going on here:<br /></li><ul><li>authmanager seems to use host, not host+port specific cookies, so when tunneling in that will give problems since everything is on localhost<br /></li></ul><ul><li>the famous sql server goes away and dies bug..</li></ul><li>it'd be nice to support &quot;friendlier&quot; titles like on wikipedia - underscore == space == %20 ... what do we need to change to do this?<br /></li><li>Attachment names and titles don't work with search, according to Phillip.</li><li>new_post/new_reply checks:</li><ul><li>details and actions should reflect the new permission stuff</li><li>do defaults for new_post/new_reply &quot;The Right Way&quot;</li><li>do something magic for the header to hide new post when it is not an option</li></ul><li>anonymous users:</li><ul><li>to do anon users justice, we'll need to rejigger details and actions to not say &quot;please log in&quot;... (could be a config option if it asks you to sign in or not?) - how about checking if there are any perms available to anoncowards, and if so, displaying them, if not displaying a more useful message than please log in (detail why we are asking to log in).<br /></li><li>Introduce a &quot;All users (including anon)&quot; option in permissions lists. which maps to * on the backend<br /></li></ul><li>wiki.tt.o folks would also like recent posts tab to be hidden from non-administrators. to do this we need a more robust templating framework for the header, etc.<br /></li><li>we can stilll get an infinite redirect under /pub/stat (others?) w/ \&quot; @ end of URL... then we get ...index.html/index.html/...<br /></li><li>email setups broken ???</li><li>NOTE: there is/was something wrong with gadgets configuration that will defeat even an index rebuild - you need to re-add all the config values, presumably so the cache is re-set.<br /></li><li>preview body should add a horizontal break and an anchor and go to that anchor...<br /></li><li>gadgets calendar is a priority</li><li>visual diff (use alt? how does wikipedia do it? also look @ trac) - use these on a) revision pages b) force_save page (the page you get when you post and someone has stolen your lock from underneath you) - also consider a JS diff - see http://en.wikipedia.org/wiki/User:Cacycle/diff<br /></li><li>wiki-source/mason/layout/post could use a rewrite/overhaul </li><li>locking stuff: catch other places a write might happen... find these with a pre_store_hook, probably.<br /></li><li>force save screen doesn't inherit layout - why? when we get diffs, this is the place to show them<br /></li><li>we should get autopopulate options for the tag field. no reason not to. particularly blog and home page but also all other tags tht exist. people DON't HAVE to click on them, and acn type their own...<br /></li><li>there seems to be some sort of off by one error in terms of who did what revision...</li><li>we die with an internal server error when we can't find the par file refeerenced in the gadget. catch this error and do somethig slightly less scary.<br /></li><li>conflicts stuff should catch the case when someone overrode your lock, and give you the option to 1) merge 2) overwrite 3) abandon<br /></li><li>add writable interface to ConfigManager, admin auths...<br /></li><li>you need to be signed on to get a list of users. not a big deal, but should be fixed eventually.<br /></li><li>deleting users seems to do funky (read: really bad) things. for now we should just set a &quot;disabled&quot; flag. That way old reference to that login in edit histories, etc. won't go ape.<br /></li><li>shouldn't ARegistration ask for your password twice and star it out as you type?<br /></li><li>search wierdness with multiple search terms?<br /></li><li>you can't search for a user by email address (only by nickname and full name) - this is actually a privacy/security FEATURE. but we should eventually give users a checkbox or so to enable this.</li><li>there is something weird with our mason stuff when you try to do a &lt;%method&gt;<br /></li><li>multi store indexing brokenness (a comma-devel bug)<br /></li><li>'GadgetsConfiguration' neq 'GadgetConfiguration', storename and dirname mismatch</li><li>have a look at Perl::Critic to analyze code for common mistakes that dug may have made ;-)</li><li>tinymce fixes:</li><ul><li>weird stuff with bullets</li><li>colors</li><li>tables/divs</li><li>raw html button<br /></li></ul><li>Audit all pre and post store hooks that do cleanup (when posts move, get deleted, when users or groups are deleted, etc) ie a post gets deleted, all previous versions should be nuked, when a user is deleted, subscription isn't deleted, etc.</li><li><b>resolved</b>: (8/23) backup scripts for chronicle repository and the chronicle xen stuff<br /></li><li><b>resolved</b>: there must be a way to hook the dummy cache_static.macro install into the make install step instead of in perl Makefile.PL</li><li><b>resolved</b>: (rev 640) Images display in image rendering program instead of coming up in firefox - fixed.</li><li><b>resolved</b>: (rev 641) this means that filenames are not preserved, which is especially problematic for non-image files... to fix, change link to file/X/Y to file/X/Y/$name to fix this ?<br /></li><li><b>resolved</b>: (rev 639) installs w/o Cache::Static require a dummy cache_static.macro (contains nothing but 1;) - we should invoke misc/install-extras.pl from Makefile.PL<br /></li><li><b>resolved</b>: (rev 637) not logged on -&gt; click &quot;new post&quot; -&gt; red &quot;you must log in&quot; message. this is check_auth not refreshing - also &quot;perhaps you should log on&quot; loads when it shouldn't if you get redirected to that page. (but a reload removes the message.) - how can we refresh without resorting to a force_auth ( redirect =&gt; 0 ) call?</li><li><b>resolved</b>: (rev 632) add config values for post_new and post_reply (needed for wiki.tt.o, and probably will be useful for others)<br /></li><li><b>resolved</b>: (rev 629) get some Cache::Static goodness up in this piece.<br /></li><li><b>resolved</b>: (rev 554) Authentication required to view photo thumbnails</li><li><b>resolved</b>: (rev 546) when a post is rendered on the front page and has an image, and has details at the top, the image shows to the left of the details and actions&gt;&gt; instead of below it. We need a br / somewhere fixed by adding CSS: .post .image-thumbnails { clear: right; }</li><li><b>resolved</b> (rev 540): locking stuff: revert/unlock &quot;button&quot; @ bottom of editor window in edit</li><li><b>resolved</b>: (rev 539) conflicts stuff is a priority (warning if it appears you have overwritten someone else's changes could work)</li><li><b>resolved</b>: (rev 532, handler.pl fix) improve poor thumbnail quality by telling GD to use true color, not pallette. see http://gadgets.xml-comma.org/post/main/03pwirBV6Sx_xqSA for example.<br /></li><li><b>resolved</b>: (rev 531, APost) gadgets gives you no indication that it can't find a user/group you've typed in by hand w/o choosing something from the autocomplete magique. make some sort of javascripty popup here.</li><li><b>resolved</b>: attachments, tags, author name, etc. should be listed at the bottom by default on wiki.tacticaltech.org, and we should have a GConf for this ( post_details_pos and post_details_elements in Wiki ). Images are now excluded from the attachments display by default. Also, add a last modified by field to this display..</li><li><b>resolved</b>: add support for user ACLs to search, etc. code</li><li><b>resolved</b>: restore sort by changed/created time in wiki-source/mason/layout/posts_table (pass an argument, remove order_by from iterator)<br /></li><li><b>resolved</b>: MRTG for watching memory usage on xen-master, update images to include snmpd with reasonable config.</li><li><b>resolved</b>: apply search permission trick to &quot;posts by&quot; and &quot;recent activity&quot;</li><li><b>resolved</b>: search now pulls any documents you have permission for<br />instead of just public posts<br /></li><li><b>resolved</b>: install osidc as 72.3.176.214</li><li><b>resolved</b>: save draft -&gt; publish broken on wiki.tt.o (probably some other subdomains too - fixed by replacing perl with standard debian perl and recompiling apache, moving/reinstalling/upgrading perl modules, etc.)<br /></li><li><b>mostly resolved</b>: also proof the css/html to make things &quot;most&quot; standard compliant</li><ul><li>as of rev 503, the following URLs pass validation:</li><ul><li>/index.html</li><li>/login.html</li><li>/pub/blog</li><li>/pub/stat</li><li>/pub/tag</li></ul><li>(no logged in pages have been validated yet)<br /></li></ul><li><b>resolved</b>: upgrade debian packages on xen-master and all xen domains for security reasons<br /></li><li><b>resolved</b>: Get xendomain template running latest gadgets modules (as of 07/08/2006). Also add apache init.d/rc*.d scripts to all xen domains for ease of restart.</li><li><b>resolved</b>: fix path handling for rss button returns Wiki par file</li><li><b>resolved</b>: layout of register link - it should be on the right</li><li><b>resolved</b>: dhandler in /wiki should check for article name match if it can't find a doc key match</li><li><b>resolved</b>: hitting a nonexistant url, such as<br /><a target="_blank" href="http://gadgets.xml-comma.org/asdfasdf/">http://gadgets.xml-comma.org/asdfasdf/</a> redirects indefinitely.<br /></li><li><b>resolved</b>: user admin should have links to user history</li><li><b>resolved</b>: $gadget = $self-&gt;conf-&gt;get( gadget =&gt; 'core_impl',<br /> path =&gt; $args{ path },<br /> key =&gt; $args{ api } ); in resolver is returning<br /> a true value when it should return a string.</li><li><b>resolved</b>: gadgets installs for travel + health on chronicle</li></ul><p></p><p>EXTRAS:</p><ul><li><b>resolved</b>: Arbitrary number of columns stored @ la footer/header in wiki-template, do the &quot;Right Thing&quot;</li><li><b>resolved</b>: by default, put quicksearch form in the header.</li><li><b>resolved</b>: so browser w/o js can at least browse gadgets sites, audit script blocks and standardize-ify them<br /></li><li>from Tami/Reed: approve-before-post functionality</li><li>from Tami/Reed: automate permissions and install process</li><li>from Tami/Reed: automagic sitemap as ttech folks were describing<br /></li><li>from Tami/Reed: friendly URLS by default, death to hashes in wiki<br /></li><li>from Tami/Reed: search by tag, etc. - more options</li><li>from Allan: we need a way to control whether main2, etc. go on the right or left hand side, because order can matter.</li><li>from Marek: need a way to find unlinked-to pages and unlinked-to pages from admin interface - what's the best way?<br /></li><li>internationalization support (low priority)<br /></li><li>rssolator-style aggregator gadget<br /></li><li>think about where we need AJAX (and the inherent round trip latency) vs. where we can get along with plain js...<br /></li><li>auto-populate tags w/ home page, blog &amp; all tags that have been used. home page &amp; blog are &quot;special&quot; and at the top. blog should default to having reply all permissions<br /></li><li>put in a &quot;permalink&quot; function that links to the title instead of the ugly hash ?<br /></li><li>commit message with each post would be nice to provide more wiki-esque functionality and to help the visual diffs.<br /></li><li>It'd be nice if the admin interface also had a web-accessible template editor.</li><li>It'd be super cool if we could seperate &quot;widgets/mini-gadgets&quot; from display. IE, you could move the navbar to the footer for example.</li><li>Allan has some good ideas about the RSS/more info links.</li><li>js autocompletion goodness for tags (but be sure to allow arbitrary input as well)</li><li>browser compatibilty (stats from mm.o, oct 3 2005):</li><ul><li>ie6 works (47%). firefox works (15%).</li></ul><ul><li>ie5.5 (3%) login seems broken?</li><li>opera (&lt;1%) seems to mostly work...<br /></li><li>safari (5%) bugs:</li><ul><li>logout is broken</li><li>details and actions dropdown falls away before you can select anything</li></ul></ul><li>18% netscape (compatible) could be ie5.5 ? (eep)</li></ul><p><br /></p><p></p>]]></description>
<pubDate>03 Jul 2006 17:08:36 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/03eJwJWUgTm0Rdlc</guid>
</item>
<item>
<title>foo</title>
<link>http://gadgets.xml-comma.org/post/main/03S_AXJ8acdBWOLI</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[<p>ham </p>]]></description>
<pubDate>26 May 2006 00:59:14 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/03S_AXJ8acdBWOLI</guid>
<enclosure url="http://gadgets.xml-comma.org/file/main/03S_AXJ8acdBWOLI/HBdLuXrp" length="111081" type="image/jpeg" />

</item>
<item>
<title>Gadgets Comparison</title>
<link>http://gadgets.xml-comma.org/post/main/03JTKRMsFJDeTtOL</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[<p>What makes Gadgets special. </p>]]></description>
<pubDate>28 Apr 2006 09:50:20 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/03JTKRMsFJDeTtOL</guid>
</item>
<item>
<title>انتخابات لاختيار زعيم جديد لليكود</title>
<link>http://gadgets.xml-comma.org/post/main/02dgFhJvtpTRskQ4</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[<p></p><p> &Oslash;&uml;&Oslash;&macr;&Oslash;&pound; &Oslash;&shy;&Oslash;&sup2;&Oslash;&uml; &Oslash;&sect;&Ugrave;&#132;&Ugrave;&#132;&Ugrave;&#138;&Ugrave;&#131;&Ugrave;&#136;&Oslash;&macr; &Oslash;&sect;&Ugrave;&#134;&Oslash;&ordf;&Oslash;&reg;&Oslash;&sect;&Oslash;&uml;&Oslash;&sect;&Oslash;&ordf; &Oslash;&macr;&Oslash;&sect;&Oslash;&reg;&Ugrave;&#132;&Ugrave;&#138;&Oslash;&copy; &Ugrave;&#132;&Oslash;&sect;&Oslash;&reg;&Oslash;&ordf;&Ugrave;&#138;&Oslash;&sect;&Oslash;&plusmn; &Oslash;&sup2;&Oslash;&sup1;&Ugrave;&#138;&Ugrave;&#133; &Ugrave;&#132;&Ugrave;&#132;&Oslash;&shy;&Oslash;&sup2;&Oslash;&uml; &Oslash;&reg;&Ugrave;&#132;&Ugrave;&#129;&Oslash;&sect; &Ugrave;&#132;&Oslash;&sect;&Oslash;&plusmn;&Ugrave;&#138;&Ugrave;&#138;&Ugrave;&#132; &Oslash;&acute;&Oslash;&sect;&Oslash;&plusmn;&Ugrave;&#136;&Ugrave;&#134; &Oslash;&sect;&Ugrave;&#132;&Oslash;&deg;&Ugrave;&#138; &Oslash;&sect;&Ugrave;&#134;&Oslash;&sup3;&Oslash;&shy;&Oslash;&uml; &Ugrave;&#133;&Ugrave;&#134; &Oslash;&sect;&Ugrave;&#132;&Ugrave;&#132;&Ugrave;&#138;&Ugrave;&#131;&Ugrave;&#136;&Oslash;&macr; &Ugrave;&#136;&Oslash;&sect;&Oslash;&sup3;&Oslash;&sup3; &Oslash;&shy;&Oslash;&sup2;&Oslash;&uml;&Oslash;&sect; &Oslash;&not;&Oslash;&macr;&Ugrave;&#138;&Oslash;&macr;&Oslash;&sect;. </p><p> &Ugrave;&#136;&Oslash;&macr;&Ugrave;&#143;&Oslash;&sup1;&Ugrave;&#138; 130 &Oslash;&sect;&Ugrave;&#132;&Ugrave;&#129;&Oslash;&sect; &Ugrave;&#133;&Ugrave;&#134; &Oslash;&sect;&Oslash;&sup1;&Oslash;&para;&Oslash;&sect;&Oslash;&iexcl; &Oslash;&sect;&Ugrave;&#132;&Oslash;&shy;&Oslash;&sup2;&Oslash;&uml; &Ugrave;&#132;&Oslash;&sect;&Oslash;&reg;&Oslash;&ordf;&Ugrave;&#138;&Oslash;&sect;&Oslash;&plusmn; &Ugrave;&#136;&Oslash;&sect;&Oslash;&shy;&Oslash;&macr; &Ugrave;&#133;&Ugrave;&#134; &Oslash;&sect;&Oslash;&plusmn;&Oslash;&uml;&Oslash;&sup1;&Oslash;&copy; &Ugrave;&#133;&Oslash;&plusmn;&Oslash;&acute;&Oslash;&shy;&Ugrave;&#138;&Ugrave;&#134; &Oslash;&sect;&Oslash;&uml;&Oslash;&plusmn;&Oslash;&sup2;&Ugrave;&#135;&Ugrave;&#133; &Ugrave;&#136;&Oslash;&sup2;&Ugrave;&#138;&Oslash;&plusmn; <a href="/util/title-link/%D8%A7%D9%84%D9%85%D8%A7%D9%84%D9%8A%D8%A9%20%D8%A7%D9%84%D8%B3%D8%A7%D8%A8%D9%82" name="gadget-cleanup-wikilink" class="">&Oslash;&sect;&Ugrave;&#132;&Ugrave;&#133;&Oslash;&sect;&Ugrave;&#132;&Ugrave;&#138;&Oslash;&copy; &Oslash;&sect;&Ugrave;&#132;&Oslash;&sup3;&Oslash;&sect;&Oslash;&uml;&Ugrave;&#130;</a> &Oslash;&uml;&Ugrave;&#134;&Ugrave;&#138;&Oslash;&sect;&Ugrave;&#133;&Ugrave;&#138;&Ugrave;&#134; &Ugrave;&#134;&Oslash;&ordf;&Ugrave;&#134;&Ugrave;&#138;&Oslash;&sect;&Ugrave;&#135;&Ugrave;&#136; &Ugrave;&#136;&Ugrave;&#136;&Oslash;&sup2;&Ugrave;&#138;&Oslash;&plusmn; &Oslash;&sect;&Ugrave;&#132;&Oslash;&reg;&Oslash;&sect;&Oslash;&plusmn;&Oslash;&not;&Ugrave;&#138;&Oslash;&copy; &Oslash;&sup3;&Ugrave;&#138;&Ugrave;&#132;&Ugrave;&#129;&Oslash;&sect;&Ugrave;&#134; &Oslash;&acute;&Oslash;&sect;&Ugrave;&#132;&Ugrave;&#136;&Ugrave;&#133;. </p>]]></description>
<pubDate>19 Dec 2005 14:31:09 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/02dgFhJvtpTRskQ4</guid>
</item>
<item>
<title>Re: Template Gadget</title>
<link>http://gadgets.xml-comma.org/post/main/02bQBtDKIGvzOiPC</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[<p>On Mon, 2005-12-12 at 17:19 +0000, David Brunton wrote:</p><blockquote><pre>&gt; I'd like to see a Template Gadget, which is used for writing
&gt; templates. See this post by Dug. The look and feel of this site is
&gt; very nice, but there are at least three Gadgets that don't really have
&gt; a look and feel- the text &quot;email header&quot; and &quot;email footer&quot; still
&gt; comes out on my emails, the registration page is plain white with no
&gt; nav (definitely okay, just weird) and the footer has the text &quot;footer
&gt; stuff&quot;.
&gt; </pre></blockquote><p>I got rid of the template placeholders for email and the footer, and set the registration page template to match the rest. Of course, I agree that we need the template editor gadget.</p><p>Changing the template of the /register/ path is as simple as pie. This was the command that I ran, showing some of the power of the configuration hooks:</p><p>sudo perl /usr/local/src/gadgets/misc/admin/add_config.pl -g ATemplate -p /register/ -key template_dir -v '/wiki-template/'</p><p>That says set the template_dir value to /wiki-template for the path /register/ for the ATemplate gadget (which we call into to display the header, footer and nav pages).</p><blockquote><pre>&gt; I was showing my wife the platform, and she noted these things right
&gt; off the bat. For demo purposes, it definitely makes a difference.
&gt; </pre></blockquote><p>I agree. Hopefully this will be a touch better.</p>]]></description>
<pubDate>12 Dec 2005 17:28:25 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/02bQBtDKIGvzOiPC</guid>
</item>
<item>
<title>Deleting a user doesn't clear subscriptions</title>
<link>http://gadgets.xml-comma.org/post/main/02bPE5_Ra1C0xJCX</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[<p>Deleting a user from the PeopleManager gadget (for administrators eyes only) doesn't clear that users email subscriptions. This tickles a bug that stops email from going out to *any* user until the subscriptions have been cleared.</p><p>From log.comma:</p><pre>Mon Dec 12 12:34:02 2005: 7537 mailing post: Post|main|02aAeWROo4m5aeQn<br />Mon Dec 12 12:34:02 2005: 7537 checking read rights for GadgetsUser|main|02Kzh3z<br />JIn2Hk-Mi<br />Mon Dec 12 12:34:02 2005: 7537 checking read rights for GadgetsUser|main|02K_2O9<br />raojF3SwS<br />Mon Dec 12 12:34:03 2005: 7537 DOC_READ_ERROR (02_6JnH3xcD7-TZt) -- could not op<br />en file '/usr/local/comma/docs/GadgetsUser/02_6JnH3xcD7-TZt.user':No such file o<br />r directory at /usr/local/lib/perl5/site_perl/5.8.7/Gadgets/Standard/Post/Email<br />.pm line 69<br /></pre><p>And that happens every minute cron runs. Eek. The obvious fix is to have an erase hook in User.pm that handles this cleanup for us.</p><p>-- Douglas Hunter</p><p></p>]]></description>
<pubDate>12 Dec 2005 16:22:30 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/02bPE5_Ra1C0xJCX</guid>
</item>
<item>
<title>re:  Gadgets goes Public</title>
<link>http://gadgets.xml-comma.org/post/main/02bP1dPsI91SQZpx</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[<p>We don't have a tempate editor gadget written yet, but it's on the list. There is a template directory on the server that has a header, footer, nav and css file that the &quot;Template&quot; gadget (which is responsible for look and feel of individual applications) points to.<br /></p>]]></description>
<pubDate>12 Dec 2005 16:09:13 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/02bP1dPsI91SQZpx</guid>
</item>
<item>
<title>re: Link to Posts from Home Page?</title>
<link>http://gadgets.xml-comma.org/post/main/02aq_-3KS-zrmwxL</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[<p>I finally realized that if we're going to let posts be self permissioned that what you described here is the Right Think (tm), and made it the default with not only this site, but future releases.</p><p>-- Douglas Hunter<br /></p>]]></description>
<pubDate>10 Dec 2005 23:48:16 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/02aq_-3KS-zrmwxL</guid>
</item>
<item>
<title>editor testing</title>
<link>http://gadgets.xml-comma.org/post/main/02aAeWROo4m5aeQn</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[<p>Testing the editor with an upgraded and slightly hacked tinyMCE. This is a <a href="/util/title-link/wikilink" name="gadget-cleanup-wikilink" class="" title="gadget-cleanup-wikilink">wikilink</a>.</p><p>This is an <a href="http://google.com">absolute link</a>.</p><p>This is a <a href="http://gadgets.xml-comma.org/new-post/relative">relative link</a>.</p><p>This is an update, did it break anything?</p><p>This is an update with an oder FireFox, did it break anything?</p><p>Can I still make a <a href="/util/title-link/wikilinks" name="gadget-cleanup-wikilink" class="" title="gadget-cleanup-wikilink">wikilink</a>?</p><p>How about an <a href="http://google.com">absolute link</a>?</p><p>And <a href="http://gadgets.xml-comma.org/edit/main/relative">realative</a>?</p>]]></description>
<pubDate>08 Dec 2005 22:58:41 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/02aAeWROo4m5aeQn</guid>
</item>
<item>
<title>User Documentation</title>
<link>http://gadgets.xml-comma.org/post/main/02_SrsDTKh4JOdt4</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[<p>Gadgets is a fully functional wiki + blog + simple CMS + threaded discussion board + mailing list manager with integrated tagging support, fulltext and tag search, multimedia attachment support, <a href="/util/title-link/RSS%20feeds" name="gadget-cleanup-wikilink" class="" title="gadget-cleanup-wikilink">RSS feeds</a>, a sophisitated permissioning system and complete email integration (with attachment support).<br /></p><p>That may sound like a lot to swallow, but we feel that these applications have been begging to intermarry for some time. It's like Emacs for the web!<br /></p><p>The first step in getting the most out of Gadgets is creating an account. This will allow you to create new posts (also via email), edit editable posts, reply to replyable posts (also via email) and manage your email subscriptions.</p><h3>Registration</h3><p>To register, click the &quot;register&quot; link in the upper right hand corner. Fill out the information on the screen and add yourself as a Gadgets user. If you uncheck &quot;enable email&quot;, you will not be emailed anything, regardless of your email subscription preferences.</p><p>Once you have registered, click on the &quot;home page&quot; link. You will be at the Gadgets home page, and notice in the upper right hand corner (logged in box) that you are now logged into Gadgets.</p><h3>Email Subscriptions</h3><p>Now that you're logged in, when you mouse over the logged in box you will see some options, including &quot;email subscriptions&quot;; follow that link. The email subscription page allows you to control you preferences globally and by group, thread, tag, or author. You don't belong to any groups yet, and won't until a site administrator adds you to a group. If you're curious, the <a href="/util/title-link/Subscription%20Algorithm" name="gadget-cleanup-wikilink" title="gadget-cleanup-wikilink" class="">Subscription Algorithm</a> has its own page, but we're going to use examples here.</p><p>If you want to receive everything from everyone, change the Global Subscription email preference to &quot;subscribed&quot;. If you don't want to receive anything from anyone, change the Global Subscription email preference to &quot;unsubscribed&quot;. If you want to receive everything except posts in the &quot;Welcome to Gadgets&quot; thread (you already feel welcome, of course), leave yourself globally subscribed, but set Welcome to Gadgets to &quot;unsubscribed&quot; under your Thread Subscription Preferences. </p><h3>Creating a New Post</h3><p>To create a new Gadgets post, click on &quot;new post&quot; in the nav bar. Give the post a title, and tag it however you would like. Please note there there are <a href="/util/title-link/Special%20Tags" name="gadget-cleanup-wikilink" title="gadget-cleanup-wikilink" class="">Special Tags</a>. If you tag a post &quot;Home Page&quot;, it will appear on the home page. If you tag a post &quot;Blog&quot;, it will appear at the blog. Attach any files you would like to the post. Images will be automatically thumbnailed for you. The default permissions are that everyone can read the post, but only you can edit or reply to the post (like a simple CMS). If you click on the &quot;Permissions...&quot; text below the attachments you can alter these permissions in any way you see fit. </p><p>This permission system lets you treat posts in any way you like. If you would like a wiki post, give &quot;All Users&quot; edit permissions. If you want a wiki + threaded discussion, give &quot;All Users&quot; edit and reply permissions. If you want a private group discussion, only add your group to the permissions.</p><p>Now add some text, using the WYSIWYG editor. There is no special WikiWord syntax to bother you. If you want to link to a page, hilight some text and click the link button. If you want to create a Wiki link (the post will be automatically created when you click the link, if it doesn't already exist), hilight some text and click the link button with the &quot;W&quot; behind it. That's it.</p><p>Feel free to save a draft if you aren't ready to make your post public yet. It will be available to you via the &quot;draft posts&quot; link in your logged in box that we discussed earlier. Or publish away! You, as the post owner, will always be able to come back and edit, reply to, or delete your post.</p><p>Once you have published your post, it will be emailed to everyone who's subscription preferences call for it (well, 5 minutes after you have published it, to give you room to make any last minute edits you need to).</p><p>That's all there is to it. Welcome to Gadgets!</p>]]></description>
<pubDate>06 Dec 2005 18:29:50 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/02_SrsDTKh4JOdt4</guid>
</item>
<item>
<title>This Site</title>
<link>http://gadgets.xml-comma.org/post/main/02_SJejQKQ-lYSc0</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[<p>xml-comma.org is driven by the <a href="/util/title-link/Gadgets%20Framework" name="gadget-cleanup-wikilink">Gadgets Framework</a>. We use the <a href="/util/title-link/People%20Manager" name="gadget-cleanup-wikilink">People Manager</a>, <a href="/util/title-link/Email%20Subscription" name="gadget-cleanup-wikilink">Email Subscription</a>, and <a href="/util/title-link/Wiki" name="gadget-cleanup-wikilink">Wiki</a> gadgets (which relies on the <a href="/util/title-link/Editor" name="gadget-cleanup-wikilink">Editor</a> and and <a href="/util/title-link/Post" name="gadget-cleanup-wikilink">Post</a> gadgets for functionality, and <a href="/util/title-link/Template" name="gadget-cleanup-wikilink">Template</a> gadget for skinablility. It also uses the <a href="/util/title-link/JUtils" name="gadget-cleanup-wikilink">JUtils</a> gadget for some JavaScript candy).</p><p></p>]]></description>
<pubDate>06 Dec 2005 18:15:06 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/02_SJejQKQ-lYSc0</guid>
</item>
<item>
<title>Welcome to Gadgets</title>
<link>http://gadgets.xml-comma.org/post/main/02_Q5bnE7nB6cgHx</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[# Gadgets Overview

Gadgets is a platform for building web applications.  Gadgets glues together mature technologies such as Apache/mod_perl, HTML::Mason, XML::Comma and MySQL.   Gadgets provides a consistant object oriented API
for common operations.

Gadgets bundles individual application hunks into PAR files (like Java's JAR files, but for Perl).  When they are installed, they announce what APIs they implement, and other application hunks get to call into them.  For instance, in the Gadgets distribution both the Wiki and Blog call into the Post gadget to store their documents, and inherit their look and feel from separate APIs implemented by the Template gadget.

This architecture allows developers to package applications into discreet, reusable components while maintaining the flexibility and power of Perl, Mason and Comma.

[[ titled_link( "Gadgets Documentation Index" ) ]]
]]></description>
<pubDate>06 Dec 2005 15:43:35 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/02_Q5bnE7nB6cgHx</guid>
</item>
<item>
<title>Gadgets goes Public</title>
<link>http://gadgets.xml-comma.org/post/main/02_6xKbt_h-FVRKg</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[<p>After writing a quick registration gadget I've taken away the logged in restriction to view gadgets.xml-comma.org. Sign-up, and post away. gadgets.xml-comma.org will be filled with useful documentation soon. </p>]]></description>
<pubDate>05 Dec 2005 17:56:37 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/02_6xKbt_h-FVRKg</guid>
</item>
<item>
<title>Gadgets Framework</title>
<link>http://gadgets.xml-comma.org/post/main/02_6XITw4jE89tM1</link>
<author>dug@allafrica.com (Douglas Hunter)</author>
<description><![CDATA[<p>Gadgets is a framework glues HTML::Mason together with XML::Comma while providing powerful hooks for authenticaiton and authorization and application configuraiton. Gadgets has built in faculties that can validate, store, manipulate and display common social software data, inlcuding structured and unstructured text (think CMS, Blog, Wiki), images (think Flickr), audio/video (think podcast) and more. Gadgets also provides a sophisticated translation interface for application authors, taking the pain out of creating translation tools with a couple simple lines of Perl.<br /><br /> The gadgets framework includes a number of complete applications, including a Blog and a Wiki, both of which have a WYSIWYG editor and tight email integration. This includes posting via email, replying to a post via email (with complete attachment support), and flexible subscriptions to threads, authors, groups, and tags.<br /><br /> Gadgets applications (which are singular gadgets) are self containted in a single zip archive, but are aware of each other, and can call mason components within each other. This makes it easy to do things such as write a new skin for a particular gadget, or make a change to how a particular view looks. A gadget can contain three directories, &quot;mason&quot;, &quot;comma&quot;, and &quot;lib&quot;. The &quot;mason&quot; directory contains all of the gadget's HTML::Mason components. The &quot;lib&quot; directory contains the gadget's controller code (we know how you like to separate your Perl from your HTML!). The &quot;comma&quot; directory contains a spec file that lets Gadgets know what interface the gadget implements, what paths to map to the gadget, and some other housekeeping data.</p><p>That's enough hype, let's start <a href="/util/title-link/Writing%20Gadgets" name="gadget-cleanup-wikilink" title="gadget-cleanup-wikilink" class="">Writing Gadgets</a>!<br /></p>]]></description>
<pubDate>05 Dec 2005 17:27:47 GMT</pubDate>
<guid>http://gadgets.xml-comma.org/post/main/02_6XITw4jE89tM1</guid>
</item>
</channel>
</rss>
