[Jifty-commit] r5222 - in jifty/branches/jquery: . bin lib lib/Jifty lib/Jifty/Action/Record lib/Jifty/Plugin/Attributes/Mixin lib/Jifty/Plugin/Comment lib/Jifty/Plugin/Comment/Action lib/Jifty/Plugin/Comment/Mixin lib/Jifty/Plugin/Comment/Mixin/Model lib/Jifty/Plugin/Comment/Model lib/Jifty/Plugin/Comment/Notification lib/Jifty/Plugin/OAuth lib/Jifty/Plugin/OAuth/Action lib/Jifty/Plugin/OAuth/Model lib/Jifty/Script lib/Jifty/TestServer lib/Jifty/View/Declare lib/Jifty/View/Static lib/Jifty/Web lib/Jifty/Web/Form share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/model share/po share/web/static/js share/web/templates/__jifty/webservices t/TestApp-JiftyJS t/TestApp-Plugin-AppPluginHasModels t/TestApp-Plugin-Attributes t/TestApp-Plugin-Attributes/t t/TestApp-Plugin-Chart t/TestApp-Plugin-Comments t/TestApp-Plugin-Comments/bin t/TestApp-Plugin-Comments/doc t/TestApp-Plugin-Comments/etc t/TestApp-Plugin-Comments/lib t/TestApp-Plugin-Comments/lib/TestApp t/TestApp-Plugin-Comments/lib/TestApp/Plugin t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/Action t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/Model t/TestApp-Plugin-Comments/log t/TestApp-Plugin-Comments/share t/TestApp-Plugin-Comments/share/po t/TestApp-Plugin-Comments/share/web t/TestApp-Plugin-Comments/share/web/static t/TestApp-Plugin-Comments/share/web/templates t/TestApp-Plugin-Comments/t t/TestApp-Plugin-Comments/var t/TestApp-Plugin-Comments/var/mason t/TestApp-Plugin-CompressedCSSandJS t/TestApp-Plugin-News t/TestApp-Plugin-OAuth t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Model t/TestApp-Plugin-OAuth/t t/TestApp-Plugin-OnClick t/TestApp-Plugin-PasswordAuth t/TestApp-Plugin-SinglePage t/TestApp/t utils

Jifty commits jifty-commit at lists.jifty.org
Sun Mar 16 17:45:41 EDT 2008


Author: gugod
Date: Sun Mar 16 17:45:40 2008
New Revision: 5222

Added:
   jifty/branches/jquery/lib/Jifty/Plugin/Comment/
   jifty/branches/jquery/lib/Jifty/Plugin/Comment.pm
   jifty/branches/jquery/lib/Jifty/Plugin/Comment/Action/
   jifty/branches/jquery/lib/Jifty/Plugin/Comment/Action/CreateComment.pm
   jifty/branches/jquery/lib/Jifty/Plugin/Comment/Dispatcher.pm
   jifty/branches/jquery/lib/Jifty/Plugin/Comment/Mixin/
   jifty/branches/jquery/lib/Jifty/Plugin/Comment/Mixin/Model/
   jifty/branches/jquery/lib/Jifty/Plugin/Comment/Mixin/Model/Commented.pm
   jifty/branches/jquery/lib/Jifty/Plugin/Comment/Model/
   jifty/branches/jquery/lib/Jifty/Plugin/Comment/Model/Comment.pm
   jifty/branches/jquery/lib/Jifty/Plugin/Comment/Notification/
   jifty/branches/jquery/lib/Jifty/Plugin/Comment/Notification/CommentNeedsModeration.pm
   jifty/branches/jquery/lib/Jifty/Plugin/Comment/Notification/CommentPublished.pm
   jifty/branches/jquery/lib/Jifty/Plugin/Comment/View.pm
   jifty/branches/jquery/lib/Jifty/Script/Repl.pm
   jifty/branches/jquery/lib/Jifty/TestServer/
   jifty/branches/jquery/lib/Jifty/TestServer/Apache.pm
   jifty/branches/jquery/t/TestApp-Plugin-Attributes/t/04-delete.t
   jifty/branches/jquery/t/TestApp-Plugin-Comments/
   jifty/branches/jquery/t/TestApp-Plugin-Comments/Makefile.PL
   jifty/branches/jquery/t/TestApp-Plugin-Comments/bin/
   jifty/branches/jquery/t/TestApp-Plugin-Comments/bin/jifty   (contents, props changed)
   jifty/branches/jquery/t/TestApp-Plugin-Comments/doc/
   jifty/branches/jquery/t/TestApp-Plugin-Comments/etc/
   jifty/branches/jquery/t/TestApp-Plugin-Comments/etc/config.yml
   jifty/branches/jquery/t/TestApp-Plugin-Comments/lib/
   jifty/branches/jquery/t/TestApp-Plugin-Comments/lib/TestApp/
   jifty/branches/jquery/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/
   jifty/branches/jquery/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/
   jifty/branches/jquery/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/Action/
   jifty/branches/jquery/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/Dispatcher.pm
   jifty/branches/jquery/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/Model/
   jifty/branches/jquery/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/Model/BlogPost.pm
   jifty/branches/jquery/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/View.pm
   jifty/branches/jquery/t/TestApp-Plugin-Comments/log/
   jifty/branches/jquery/t/TestApp-Plugin-Comments/share/
   jifty/branches/jquery/t/TestApp-Plugin-Comments/share/po/
   jifty/branches/jquery/t/TestApp-Plugin-Comments/share/web/
   jifty/branches/jquery/t/TestApp-Plugin-Comments/share/web/static/
   jifty/branches/jquery/t/TestApp-Plugin-Comments/share/web/templates/
   jifty/branches/jquery/t/TestApp-Plugin-Comments/t/
   jifty/branches/jquery/t/TestApp-Plugin-Comments/t/00-model-BlogPost.t
   jifty/branches/jquery/t/TestApp-Plugin-Comments/var/
   jifty/branches/jquery/t/TestApp-Plugin-Comments/var/mason/
   jifty/branches/jquery/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Model/Favorite.pm
   jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/06-read-only.t
   jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/07-read-write.t
   jifty/branches/jquery/utils/
   jifty/branches/jquery/utils/js_size.pl
Modified:
   jifty/branches/jquery/   (props changed)
   jifty/branches/jquery/META.yml
   jifty/branches/jquery/Makefile.PL
   jifty/branches/jquery/bin/jifty
   jifty/branches/jquery/lib/Jifty.pm
   jifty/branches/jquery/lib/Jifty/Action.pm
   jifty/branches/jquery/lib/Jifty/Action/Record/Delete.pm
   jifty/branches/jquery/lib/Jifty/Action/Record/Update.pm
   jifty/branches/jquery/lib/Jifty/ClassLoader.pm
   jifty/branches/jquery/lib/Jifty/Handle.pm
   jifty/branches/jquery/lib/Jifty/Handler.pm
   jifty/branches/jquery/lib/Jifty/Logger.pm
   jifty/branches/jquery/lib/Jifty/Plugin/Attributes.pm
   jifty/branches/jquery/lib/Jifty/Plugin/Attributes/Mixin/Attributes.pm
   jifty/branches/jquery/lib/Jifty/Plugin/CSSQuery.pm
   jifty/branches/jquery/lib/Jifty/Plugin/CompressedCSSandJS.pm
   jifty/branches/jquery/lib/Jifty/Plugin/Monitoring.pm
   jifty/branches/jquery/lib/Jifty/Plugin/OAuth.pm
   jifty/branches/jquery/lib/Jifty/Plugin/OAuth/Action/AuthorizeRequestToken.pm
   jifty/branches/jquery/lib/Jifty/Plugin/OAuth/Dispatcher.pm
   jifty/branches/jquery/lib/Jifty/Plugin/OAuth/Model/AccessToken.pm
   jifty/branches/jquery/lib/Jifty/Plugin/OAuth/Model/Consumer.pm
   jifty/branches/jquery/lib/Jifty/Plugin/OAuth/Model/RequestToken.pm
   jifty/branches/jquery/lib/Jifty/Plugin/OAuth/View.pm
   jifty/branches/jquery/lib/Jifty/Plugin/REST.pm
   jifty/branches/jquery/lib/Jifty/Plugin/Recorder.pm
   jifty/branches/jquery/lib/Jifty/Request.pm
   jifty/branches/jquery/lib/Jifty/Script/App.pm
   jifty/branches/jquery/lib/Jifty/Test.pm
   jifty/branches/jquery/lib/Jifty/Util.pm
   jifty/branches/jquery/lib/Jifty/View/Declare/CRUD.pm
   jifty/branches/jquery/lib/Jifty/View/Declare/Helpers.pm
   jifty/branches/jquery/lib/Jifty/View/Static/Handler.pm
   jifty/branches/jquery/lib/Jifty/Web.pm
   jifty/branches/jquery/lib/Jifty/Web/Form.pm
   jifty/branches/jquery/lib/Jifty/Web/Form/Field.pm
   jifty/branches/jquery/lib/Jifty/Web/PageRegion.pm
   jifty/branches/jquery/share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list/list
   jifty/branches/jquery/share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list/new_item
   jifty/branches/jquery/share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list/search
   jifty/branches/jquery/share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list/update
   jifty/branches/jquery/share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list/view
   jifty/branches/jquery/share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/model/dhandler
   jifty/branches/jquery/share/po/zh_cn.po
   jifty/branches/jquery/share/po/zh_tw.po
   jifty/branches/jquery/share/web/static/js/jifty.js
   jifty/branches/jquery/share/web/templates/__jifty/webservices/xml
   jifty/branches/jquery/t/TestApp-JiftyJS/Makefile.PL
   jifty/branches/jquery/t/TestApp-Plugin-AppPluginHasModels/Makefile.PL
   jifty/branches/jquery/t/TestApp-Plugin-Attributes/   (props changed)
   jifty/branches/jquery/t/TestApp-Plugin-Attributes/Makefile.PL
   jifty/branches/jquery/t/TestApp-Plugin-Chart/Makefile.PL
   jifty/branches/jquery/t/TestApp-Plugin-CompressedCSSandJS/Makefile.PL
   jifty/branches/jquery/t/TestApp-Plugin-News/Makefile.PL
   jifty/branches/jquery/t/TestApp-Plugin-OAuth/Makefile.PL
   jifty/branches/jquery/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Dispatcher.pm
   jifty/branches/jquery/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Model/User.pm
   jifty/branches/jquery/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Test.pm
   jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/00-test-setup.t
   jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/01-basic.t
   jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/02-request-token.t
   jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/03-authorize.t
   jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/04-access-token.t
   jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/05-protected-resource.t
   jifty/branches/jquery/t/TestApp-Plugin-OnClick/Makefile.PL
   jifty/branches/jquery/t/TestApp-Plugin-PasswordAuth/Makefile.PL
   jifty/branches/jquery/t/TestApp-Plugin-SinglePage/Makefile.PL
   jifty/branches/jquery/t/TestApp/t/15-template-subclass.t
   jifty/branches/jquery/t/TestApp/t/16-template-region.t

Log:
- Merge /prj/mirror/jifty/trunk to /prj/mirror/jifty/branches/jquery

Modified: jifty/branches/jquery/META.yml
==============================================================================
--- jifty/branches/jquery/META.yml	(original)
+++ jifty/branches/jquery/META.yml	Sun Mar 16 17:45:40 2008
@@ -3,7 +3,7 @@
 build_requires: 
   ExtUtils::MakeMaker: 6.11
 distribution_type: module
-generated_by: Module::Install version 0.680
+generated_by: Module::Install version 0.68
 license: Perl
 meta-spec: 
   url: http://module-build.sourceforge.net/META-spec-v1.3.html
@@ -33,6 +33,7 @@
   Devel::EvalContext: 0
   Devel::Events::Objects: 0.02
   Devel::Gladiator: 0
+  Devel::REPL: 0
   Devel::Size: 0
   Digest::HMAC_SHA1: 0
   GD: 0
@@ -42,6 +43,7 @@
   Module::CoreList: 0
   Module::Install::Admin: 0.50
   Module::Refresh: 0.09
+  Net::Akismet: 0
   Net::LDAP: 0
   Net::OAuth::Request: 0.05
   Net::OpenID::Consumer: 0
@@ -68,6 +70,7 @@
   Class::Accessor: 0
   Class::Container: 0
   Class::Data::Inheritable: 0
+  Class::Inspector: 1.2
   Class::Trigger: 0.12
   Clone: 0.27
   Compress::Zlib: 0
@@ -99,6 +102,7 @@
   HTML::Lint: 0
   HTML::Mason: 1.3101
   HTML::Mason::Plugin: 0
+  HTML::Scrubber: 0
   HTTP::Cookies: 0
   HTTP::Date: 0
   HTTP::Server::Simple: 0.28
@@ -107,12 +111,13 @@
   Hook::LexWrap: 0
   IPC::PubSub: 0.23
   IPC::Run3: 0
-  JSON::Syck: 0.15
+  JSON::Syck: 0.29
   Jifty::DBI: 0.49
   LWP::UserAgent: 0
   Locale::Maketext::Extract: 0.20
   Locale::Maketext::Lexicon: 0.60
   Log::Log4perl: 1.04
+  MIME::Base64::URLSafe: 0
   MIME::Types: 0
   Module::CoreList: 0
   Module::Pluggable: 3.5
@@ -123,6 +128,7 @@
   PadWalker: 0
   Params::Validate: 0
   Pod::Simple: 0
+  Regexp::Common: 0
   SQL::ReservedWords: 0
   Scalar::Defer: 0.12
   Shell::Command: 0
@@ -147,4 +153,4 @@
   perl: 5.8.3
   version: 0
 tests: t/*.t t/*/*.t t/*/*/*.t t/*/*/*/*.t
-version: 0.71129
+version: 0.80311

Modified: jifty/branches/jquery/Makefile.PL
==============================================================================
--- jifty/branches/jquery/Makefile.PL	(original)
+++ jifty/branches/jquery/Makefile.PL	Sun Mar 16 17:45:40 2008
@@ -92,11 +92,11 @@
     requires('YAML' => 0.35); 	# Use YAML::Dump for the moment since YAML.pm segfaults on
 				# reading stupidly long (~20K characters) double-quoted
 				# strings, and we need to produce YAML.pm-readable output.
-    requires('JSON::Syck' => 0.15);
+    requires('JSON::Syck' => 0.29);
 }
 else {
     requires('YAML' => 0.35) unless can_use('YAML::Syck' => 0.71);
-    requires('JSON' => 0.01) unless can_use('JSON::Syck' => 0.15);
+    requires('JSON' => 0.01) unless can_use('JSON::Syck' => 0.29);
 }
 
 features(
@@ -154,6 +154,10 @@
         -default => 0,
         recommends('Devel::EvalContext'),
     ],
+    'Jifty REPL' => [
+        -default => 0,
+        recommends('Devel::REPL'), # Devel::REPL::Script
+    ],
     'Chart Plugin (none of these must be installed for Charts to work)' => [
         -default => 0,
         recommends('Chart::Base'),
@@ -188,6 +192,13 @@
         recommends('Template::Declare' => '0.28'),
         recommends('Data::Dump::Streamer'),
     ],
+    'Comment Plugin' => [
+        -default => 0,
+        requires('HTML::Scrubber'),
+        requires('MIME::Base64::URLSafe'),
+        recommends('Net::Akismet'),
+        requires('Regexp::Common'),
+    ],
 );
 
 

Modified: jifty/branches/jquery/bin/jifty
==============================================================================
--- jifty/branches/jquery/bin/jifty	(original)
+++ jifty/branches/jquery/bin/jifty	Sun Mar 16 17:45:40 2008
@@ -1,7 +1,6 @@
 #!/usr/bin/env perl
 use warnings;
 use strict;
-use File::Basename qw(dirname); 
 use UNIVERSAL::require;
 
 use Jifty;

Modified: jifty/branches/jquery/lib/Jifty.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty.pm	(original)
+++ jifty/branches/jquery/lib/Jifty.pm	Sun Mar 16 17:45:40 2008
@@ -13,7 +13,7 @@
     require Time::Local;
 
     # Declare early to make sure Jifty::Record::schema_version works
-    $Jifty::VERSION = '0.71129';
+    $Jifty::VERSION = '0.80311';
 }
 
 =head1 NAME

Modified: jifty/branches/jquery/lib/Jifty/Action.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Action.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Action.pm	Sun Mar 16 17:45:40 2008
@@ -520,13 +520,10 @@
             Jifty->log->warn("$arg_name isn't a valid field for $self");
         }
     } 
-    
     # It has been cached, but render_as is explicitly set
-    elsif ( $args{render_as} ) {
+    elsif ( my $widget = $args{render_as} ) {
+        $self->{_private_form_fields_hash}{$arg_name}->rebless( $widget );
 
-        # Rebless the form control as something else
-        bless $self->{_private_form_fields_hash}{$arg_name},
-          "Jifty::Web::Form::Field::".ucfirst($args{render_as});
     }
 
     return $self->{_private_form_fields_hash}{$arg_name};
@@ -957,7 +954,7 @@
     if ( $value && $field_info->{valid_values} ) {
 
         # If you're not on the list, you can't come to the party
-        unless ( grep $_->{'value'} eq $value,
+        unless ( grep {defined $_->{'value'} and $_->{'value'} eq $value}
             @{ $self->valid_values($field) } ) {
 
             return $self->validation_error(

Modified: jifty/branches/jquery/lib/Jifty/Action/Record/Delete.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Action/Record/Delete.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Action/Record/Delete.pm	Sun Mar 16 17:45:40 2008
@@ -58,7 +58,7 @@
 
     # Delete the record and return an error if delete fails
     my ( $val, $msg ) = $self->record->delete;
-    $self->result->error($msg) if not $val and $msg;
+    $self->result->error($msg || _('Permission denied')) if not $val;
 
     # Otherwise, we seem to have succeeded, report that
     $self->report_success if not $self->result->failure;

Modified: jifty/branches/jquery/lib/Jifty/Action/Record/Update.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Action/Record/Update.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Action/Record/Update.pm	Sun Mar 16 17:45:40 2008
@@ -175,8 +175,8 @@
         # Calculate the name of the setter and set; asplode on failure
         my $setter = "set_$field";
         my ( $val, $msg ) = $self->record->$setter( $value );
-        $self->result->field_error($field, $msg)
-          if not $val and $msg;
+        $self->result->field_error($field, $msg || _('Permission denied'))
+          if not $val;
 
         # Remember that we changed something (if we did)
         $changed = 1 if $val;

Modified: jifty/branches/jquery/lib/Jifty/ClassLoader.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/ClassLoader.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/ClassLoader.pm	Sun Mar 16 17:45:40 2008
@@ -220,19 +220,27 @@
     elsif ( $module =~ /^(?:$base)::Action::
                         (Create|Update|Delete|Search)([^\.]+)$/x ) {
 
+        my $action = $1;
+        my $model  = $2;
+
         # Determine the model class and load it
-        my $modelclass = $base . "::Model::" . $2;
+        my $modelclass = $base . "::Model::" . $model;
         Jifty::Util->_require( module => $modelclass, quiet => 1);
 
         # Don't generate the action unless it really is a model
         if ( eval { $modelclass->isa('Jifty::Record') } ) {
-            if ($modelclass->autogenerate_action($1)) {
-                $AUTOGENERATED{$module} = 1;
-                return $self->return_class(
-                      "package $module;\n"
-                    . "use base qw/$base\::Action::Record::$1/;\n"
-                    . "sub record_class { '$modelclass' };\n"
-                );
+            if ($modelclass->autogenerate_action($action)) {
+
+                # Skip autogenerated models; that is, those that are overridden
+                # by plugins, the special case below should take care of it
+                if (!$self->autogenerated($modelclass)) {
+                    $AUTOGENERATED{$module} = 1;
+                    return $self->return_class(
+                        "package $module;\n"
+                        . "use base qw/$base\::Action::Record::$action/;\n"
+                        . "sub record_class { '$modelclass' };\n"
+                    );
+                }
             }
         }
 

Modified: jifty/branches/jquery/lib/Jifty/Handle.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Handle.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Handle.pm	Sun Mar 16 17:45:40 2008
@@ -129,37 +129,22 @@
 
     # Application db version check
     {
-        my $dbv  = Jifty::Model::Metadata->load("application_db_version");
         my $appv = Jifty->config->framework('Database')->{'Version'};
+        my $dbv = $self->_fetch_dbv;
 
-        if ( not defined $dbv ) {
+        unless (defined $dbv) {
+            warn "Application schema has no version in the database.\n";
 
-      # First layer of backwards compatibility -- it used to be in _db_version
-            my @v;
-            eval {
-                local $SIG{__WARN__} = sub { };
-                @v = Jifty->handle->fetch_result(
-                    "SELECT major, minor, rev FROM _db_version");
-            };
-            $dbv = join( ".", @v ) if @v == 3;
-        }
-        if ( not defined $dbv ) {
+            # we can just create it and we'll be up to date
+            if ( $autoup ) {
+                warn "Automatically creating your database.\n";
+                $self->_create_original_database();
+                return 1;
+            }
 
-            # It was also called the 'key' column, not the data_key column
-            eval {
-                local $SIG{__WARN__} = sub { };
-                $dbv
-                    = Jifty->handle->fetch_result(
-                    "SELECT value FROM _jifty_metadata WHERE key = 'application_db_version'"
-                    );
-            } or undef($dbv);
+            die "Please run `bin/jifty schema --setup` to create the database.\n";
         }
 
-        die
-            "Application schema has no version in the database; perhaps you need to run this:\n"
-            . "\t bin/jifty schema --setup\n"
-            unless defined $dbv;
-
         unless ( version->new($appv) == version->new($dbv) ) {
 
         # if app version is older than db version, but we are still compatible
@@ -172,7 +157,7 @@
                     "Application schema version in database ($dbv) doesn't match application schema version ($appv)\n";
                 if ( $autoup ) {
                     warn
-                        "Automatically upgrading your database to match the current application schema";
+                        "Automatically upgrading your database to match the current application schema.\n";
                     $self->_upgrade_schema();
                 } else {
                     die
@@ -292,6 +277,20 @@
     }
 }
 
+sub _create_original_database {
+    my $self = shift;
+
+    my $hack = {};
+    require Jifty::Script::Schema;
+    bless $hack, "Jifty::Script::Schema";
+    $hack->create_all_tables;
+
+    # reconnect for consistency
+    # SQLite complains about the schema being changed
+    $self->disconnect;
+    $self->connect;
+}
+
 sub _upgrade_schema {
     my $self = shift;
 
@@ -299,7 +298,32 @@
     require Jifty::Script::Schema;
     bless $hack, "Jifty::Script::Schema";
     $hack->run_upgrades;
+}
+
+sub _fetch_dbv {
+    my $self = shift;
+
+    my $dbv = Jifty::Model::Metadata->load("application_db_version");
+    return $dbv if defined $dbv;
+
+    # First layer of backwards compatibility -- it used to be in _db_version
+    eval {
+        local $SIG{__WARN__} = sub { };
+        my @v = Jifty->handle->fetch_result(
+            "SELECT major, minor, rev FROM _db_version");
+        return join( ".", @v ) if @v == 3;
+    };
+
+    # Second layer -- it was also called the 'key' column, not the data_key column
+    eval {
+        local $SIG{__WARN__} = sub { };
+        return Jifty->handle->fetch_result(
+            "SELECT value FROM _jifty_metadata WHERE key = 'application_db_version'"
+        );
+    };
 
+    # most likely no database exists
+    return undef;
 }
 
 =head1 AUTHOR

Modified: jifty/branches/jquery/lib/Jifty/Handler.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Handler.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Handler.pm	Sun Mar 16 17:45:40 2008
@@ -242,8 +242,9 @@
         Jifty::I18N->get_language_handle;
 
         # Return from the continuation if need be
-        Jifty->web->request->return_from_continuation;
-        $self->dispatcher->handle_request();
+        unless (Jifty->web->request->return_from_continuation) {
+            $self->dispatcher->handle_request();
+        } 
 
         $self->call_trigger('before_cleanup', $args{cgi});
 
@@ -262,8 +263,10 @@
 
 sub cleanup_request {
     my $self = shift;
+
     # Clean out the cache. the performance impact should be marginal.
     # Consistency is improved, too.
+
     Jifty->web->session->unload();
     Jifty::Record->flush_cache if Jifty::Record->can('flush_cache');
     $self->cgi(undef);

Modified: jifty/branches/jquery/lib/Jifty/Logger.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Logger.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Logger.pm	Sun Mar 16 17:45:40 2008
@@ -36,7 +36,7 @@
 The default log configuration that logs all messages to the screen
 (i.e. to STDERR, be that directly to the terminal or to fastcgi's
 log file.)  It will log all messages of equal or higher priority
-to he LogLevel configuration option.
+to the LogLevel configuration option.
 
     --- 
     framework: 

Modified: jifty/branches/jquery/lib/Jifty/Plugin/Attributes.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Plugin/Attributes.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Plugin/Attributes.pm	Sun Mar 16 17:45:40 2008
@@ -4,5 +4,19 @@
 package Jifty::Plugin::Attributes;
 use base 'Jifty::Plugin';
 
+our $VERSION = '0.01';
+
+sub init {
+    Jifty::DBI::Record->add_trigger(
+        name      => "after_delete",
+        callback  => sub {
+            my $record = shift;
+            if ($record->can('delete_all_attributes')) {
+                $record->delete_all_attributes;
+            }
+        },
+    );
+}
+
 1;
 

Modified: jifty/branches/jquery/lib/Jifty/Plugin/Attributes/Mixin/Attributes.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Plugin/Attributes/Mixin/Attributes.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Plugin/Attributes/Mixin/Attributes.pm	Sun Mar 16 17:45:40 2008
@@ -8,7 +8,7 @@
 use base 'Exporter';
 
 our @EXPORT = qw/attributes first_attribute add_attribute set_attribute
-                 delete_attribute/;
+                 delete_attribute delete_all_attributes/;
 
 =head2 attributes
 
@@ -140,5 +140,24 @@
     return $ok;
 }
 
+=head2 delete_all_attributes
+
+Deletes all the attributes associated with the object.
+
+=cut
+
+sub delete_all_attributes {
+    my $self = shift;
+    my $ok = 1;
+
+    my $attrs = $self->attributes;
+    while (my $attr = $attrs->next) {
+        $attr->delete
+            or $ok = 0;
+    }
+
+    return $ok;
+}
+
 1;
 

Modified: jifty/branches/jquery/lib/Jifty/Plugin/CSSQuery.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Plugin/CSSQuery.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Plugin/CSSQuery.pm	Sun Mar 16 17:45:40 2008
@@ -37,11 +37,16 @@
 =head2 init
 
 This initializes the plugin, which simply includes the JavaScript
-necessary to load cssQuery.
+necessary to load cssQuery, and gets rid of the cssQuery-jquery back-compat
+script.
 
 =cut
 
 sub init {
+    Jifty->web->remove_javascript(
+        'cssQuery-jquery.js',
+    );
+
     Jifty->web->add_javascript(
         'cssquery/cssQuery.js',
         'cssquery/cssQuery-level2.js',

Added: jifty/branches/jquery/lib/Jifty/Plugin/Comment.pm
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/lib/Jifty/Plugin/Comment.pm	Sun Mar 16 17:45:40 2008
@@ -0,0 +1,207 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Comment;
+use base qw/ Jifty::Plugin /;
+
+__PACKAGE__->mk_accessors( qw/ akismet scrubber scrub_message / );
+
+use HTML::Scrubber;
+
+=head1 NAME
+
+Jifty::Plugin::Comment - Add comments to any record
+
+=head1 SYNOPSIS
+
+Setup the F<config.yml>
+
+  Plugins:
+    - Comment:
+      
+      # Set this if you want spam checking by Net::Akismet
+      Akismet:
+        Key: 1234567890a
+        Url: http://example.com
+
+      # Set this if you want to customize the HTML scrubbing of comments
+      Scrubber:
+        message: "Comments may only contain <strong>, <em>, and <a> tags."
+        allow:
+          - strong
+          - em
+          - a
+        default:
+          - 0
+          - 
+            '*': 0
+            href: !!perl/regexp:
+              REGEXP: '^(?!(?:java)?script)'
+              MODIFIERS: i
+
+Setup a model that has comments:
+
+  package App::Model::Fooble;
+  
+  use Jifty::DBI::Schema;
+  use App::Record schema {
+      column scribble => type is 'text';
+      column wobble => type is 'int';
+  };
+
+  use Jifty::Plugin::Comment::Mixin::Model::Commented;
+
+  App::Model::FoobleComment->add_trigger( before_access => sub {
+      my $self = shift;
+      my ($right, %args) = @_;
+
+      if ($right eq 'create') {
+          return 1 if $self->current_user->id;
+      }
+
+      if ($right eq 'read') {
+          return 1;
+      }
+
+      return $self->App::Model::FoobleComment::current_user_can(@_);
+  });
+
+Setup a view for creating, viewing, and managing the comments:
+
+  # assuming $fooble here isa App::Action::UpdateFooble object
+  template 'fooble/view' => page {
+      my $fooble = get 'fooble';
+
+      render_action $fooble, undef, { render_mode => 'read' };
+
+      render_region
+          name     => 'fooble-comments',
+          path     => '__comment/list_and_add',
+          defaults => { 
+              comment_upon  => $fooble->record->for_commenting,
+              initial_title => 'Re: '.substr($fooble->scribble, 0, 20).'...',
+          },
+          ;
+  };
+
+=head1 DESCRIPTION
+
+This plugin allows you to attach comments to any model. You do this using the three steps listed in the synopsis. For variations on these steps, see the other classes that handle the individual parts.
+
+=head1 COMMENTED RECORDS
+
+To set up a commented model, you will need to do the following:
+
+=over
+
+=item 1 Add ths plugin to your project by modifying your F<config.yml>.
+
+=item 1 Add the L<Jifty::Plugin::Comment::Mixin::Model::Commented> mixin to the model or models that you want to have comments attached to. See that class for details on how it works. You may also want to examine L<Jifty::Plugin::Comment::Model::Comment> on how to customize that class for your application.
+
+=item 1 Create a view that appends a comment editor to your edit form (or on a separate page or wherever you feel like comments work best in your application). You should be able to use these views from either L<Template::Declare> or L<HTML::Mason> templates. See L<Jifty::Plugin::Comment::View> for additional details on what views are available.
+
+=back
+
+=head1 METHODS
+
+=head2 init
+
+Called during initialization. This will setup the L<Net::Akismet> object if it is configured and available.
+
+=cut
+
+sub init {
+    my $self = shift;
+
+    $self->_init_akismet(@_);
+    $self->_init_scrubber(@_);
+}
+
+sub _init_akismet {
+    my $self = shift;
+    my %args = @_;
+
+    # Stop now if we don't have the Akismet thing
+    return unless defined $args{Akismet};
+
+    # Check for the Akismet options
+    my $key = $args{Akismet}{Key};
+    my $url = $args{Akismet}{Url};
+
+    # Don't go forward unless we have a key and a URL configured
+    return unless $key and $url;
+
+    # Try to load Akismet first...
+    eval "use Net::Akismet";
+    if ($@) {
+        Jifty->log->error("Failed to load Net::Akismet. Your comments will not be checked for link spam and the like. $@");
+        return;
+    }
+
+    # Now get our object
+    my $akismet = Net::Akismet->new( KEY => $key, URL => $url );
+
+    unless ($akismet) {
+        Jifty->log->error("Failed to verify your Akismet key. Your comments will not be checked for link spam and the like.");
+        return;
+    }
+
+    $self->akismet($akismet);
+}
+
+sub _init_scrubber {
+    my $self = shift;
+    my %args = @_;
+
+    my $scrubber_args = $args{Scrubber};
+    if (not defined $scrubber_args) {
+        $scrubber_args = {
+            message => 'Comments may only contain <strong>, <em>, and <a> tags.'
+                      .' Anything else will be removed.',
+            allow   => [ qw/ strong em a / ],
+            default => [ 0, { '*' => 0, 'href' => qr{^(?!(?:java)?script)}i } ],
+        };
+    }
+
+    my $scrub_message = delete $scrubber_args->{message};
+    $scrub_message = 'The text you have given will be cleaned up.'
+        unless $scrub_message;
+
+    $self->scrub_message($scrub_message);
+
+    my $scrubber = HTML::Scrubber->new( %$scrubber_args );
+
+    $self->scrubber($scrubber);
+}
+
+=head2 akismet
+
+This returns an instance of L<Net::Akismet> that is used to check to see if a new comment posted contains spam. No such checking is performed if this returns C<undef>, which indicates that C<Net::Akismet> is unavailable, wasn't configured, or there was an error configuring it (e.g., the Akismet server was unavailable during Jifty startup).
+
+=head2 scrubber
+
+This returns an instance of L<HTML::Scrubber> that is used to clean up HTML submitted in comments.
+
+=head1 TO DO
+
+Right now the module depends directly upon L<HTML::Scrubber> to do the work of cleaning up the text. You might want to use something else to do this. It also provides no mechanism for customizing any other aspect of the formatting. For example, your application might want to use Markdown, or BBCode, or just turn line breaks in the BR-tags, or anything else to format the comment text.
+
+In the future, I'd like to consider something like L<Text::Pipe> or a similar API to allow these formats to be customized more easily.
+
+=head1 SEE ALSO
+
+L<Net::Akismet>, L<HTML::Scrubber>
+
+=head1 AUTHOR
+
+Andrew Sterling Hanenkamp, C<< <hanenkamp at cpan.org> >>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Boomer Consulting, Inc. All Rights Reserved.
+
+This program is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
+1;

Added: jifty/branches/jquery/lib/Jifty/Plugin/Comment/Action/CreateComment.pm
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/lib/Jifty/Plugin/Comment/Action/CreateComment.pm	Sun Mar 16 17:45:40 2008
@@ -0,0 +1,435 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Comment::Action::CreateComment;
+use base qw/ Jifty::Action::Record::Create /;
+
+=head1 NAME
+
+Jifty::Plugin::Comment::Action::CreateComment - custom CreateComment that attaches the comment to the parent
+
+=head1 DESCRIPTION
+
+This is a specialized create action that attaches the comment to the parent object.
+
+=head1 SCHEMA
+
+=head2 parent_class
+
+This is the parent model class. This class must use the L<Jifty::Plugin::Comment::Mixin::Model::Commented> mixin.
+
+=head2 parent_id
+
+This is the ID of the object to attach the comment to.
+
+=head2 title
+
+This is the title the author of the comment has given it.
+
+=head2 your_name
+
+This is the name of the author of the comment.
+
+=head2 web_site
+
+This is the (optional) web site of the author of the comment.
+
+=head2 email
+
+This is the (optional) email address of the author of the comment.
+
+=head2 body
+
+This is the comment message.
+
+=head2 published
+
+This is true if the comment should be published or false if it is only visible to moderators.
+
+=head2 created_on
+
+This is the timestamp of the comment's creation.
+
+=head2 status
+
+This is string with either the value "spam" for a message that has been flagged as spam or "ham" for a message that is not spam.
+
+=head2 http_referer
+
+The referer claimed by the client.
+
+=head2 http_user_agent
+
+The user agent claimed by the client.
+
+=head2 ip_addr
+
+The IP address of the client.
+
+=cut
+
+use Jifty::Param::Schema;
+use Jifty::Action::Record::Create schema {
+    param parent_class =>
+        type is 'hidden',
+        is mandatory,
+        order is 1,
+        ;
+
+    param parent_id =>
+        type is 'hidden',
+        is mandatory,
+        order is 2,
+        ;
+
+    param title =>
+        label is 'Title',
+        is mandatory,
+        ajax validates,
+        is focus,
+        order is 3,
+        ;
+
+    param your_name =>
+        label is 'Your name',
+        default is defer { from_cookie(0) },
+        # TODO This is canonicalizing at the wrong time, I need another way.
+#        ajax canonicalizes,
+        order is 4,
+        ;
+
+    param web_site =>
+        label is 'Web site',
+        default is defer { from_cookie(1) },
+        ajax validates,
+        order is 5,
+        ;
+
+    param email =>
+        label is 'Email address',
+        default is defer { from_cookie(2) },
+        ajax validates,
+        order is 6,
+        ;
+
+#    param remember_me =>
+#        type is 'checkbox',
+#        label is 'Remember me',
+#        hints is 'Check this box for this site to store a cookie on your browser that is used to automatically fill in your name, email address, and web site the next time you make a comment.',
+#        ;
+
+    param body =>
+        type is 'textarea',
+        label is 'Comment',
+        is mandatory,
+        ajax validates,
+        order is 7,
+        ;
+
+    param published =>
+        type is 'unrendered',
+        render as 'unrendered',
+        mandatory is 0,
+        ;
+
+    param created_on =>
+        type is 'unrendered',
+        render as 'unrendered',
+        ;
+
+    param status =>
+        type is 'unrendered',
+        render as 'unrendered',
+        ;
+
+    param http_referer =>
+        type is 'unrendered',
+        render as 'unrendered',
+        ;
+
+    param http_user_agent =>
+        type is 'unrendered',
+        render as 'unrendered',
+        ;
+
+    param ip_addr =>
+        type is 'unrendered',
+        render as 'unrendered',
+        ;
+};
+
+use CGI::Cookie;
+use MIME::Base64::URLSafe;
+#use Contentment::Notification::CommentPublished;
+#use Contentment::Notification::CommentNeedsModeration;
+#use Contentment::Util;
+use Regexp::Common qw/ Email::Address URI /;
+
+=head1 METHODS
+
+=head2 record_class
+
+Returns the application's comment class.
+
+=cut
+
+sub record_class { Jifty->app_class('Model', 'Comment') }
+
+=head2 parent
+
+This converts the "parent_id" and "parent_class" arguments into an object.
+
+=cut
+
+sub parent {
+    my $self = shift;
+
+    my $parent_class = $self->argument_value('parent_class');
+    my $parent_id    = $self->argument_value('parent_id');
+
+    my $parent = $parent_class->new;
+    $parent->load($parent_id);
+
+    return $parent;
+}
+
+=head2 take_action
+
+Performs the work of attaching the comment to the parent object.
+
+=cut
+
+sub take_action {
+    my $self = shift;
+
+    if ($self->argument_value('submit')) {
+        my $your_name     = urlsafe_b64encode($self->argument_value('your_name'));
+        my $web_site      = urlsafe_b64encode($self->argument_value('web_site'));
+        my $email = urlsafe_b64encode(
+            $self->argument_value('email'));
+
+        my $cookie = CGI::Cookie->new(
+            -path    => '/',
+            -name    => 'COMMENT_REMEMBORY',
+            -value   => join('.', $your_name, $web_site, $email),
+            -expires => '+3M',
+        );
+
+        Jifty->web->response->add_header( 'Set-Cookie' => $cookie->as_string );
+
+        $self->SUPER::take_action(@_);
+
+        # On success, create the extra link record
+        if ($self->result->success) {
+            my $parent_class = $self->argument_value('parent_class');
+            my $link = $parent_class->comment_record_class->new;
+            $link->create(
+                commented_upon => $self->parent,
+                the_comment    => $self->record,
+            );
+
+            # Link failed?
+            unless ($link->id) {
+                $self->log->error("Failed to create the comment and linking record required for comment #@{[$self->record->id]} and parent record class @{[$self->argument_value('parent_class')]} #@{[$self->argument_value('parent_id')]}");
+                $self->result->message(undef);
+                $self->result->error("Failed to create the comment and linking record required. This error has been logged.");
+            }
+
+            # Link succeeded! This comment's training is complete!
+            else {
+                # Send notification by email to the site owners
+                my $notification_class 
+                    = $self->record->published ? 'CommentPublished'
+                    :                            'CommentNeedsModeration';
+                $notification_class 
+                    = Jifty->app_class('Notification', $notification_class);
+                my $notification = $notification_class->new( 
+                    comment => $self->record, 
+                    parent  => $self->parent,
+                );
+                $notification->send;
+            }
+        }
+    }
+    else {
+        $self->result->message(qq{Previewing your comment titled "@{[$self->argument_value('title') || 'Untitled']}"});
+        $self->result->failure(1);
+    }
+}
+
+=head2 report_success
+
+Reports success or the need for moderation of the message.
+
+=cut
+
+sub report_success {
+    my $self = shift;
+    $self->result->message(_("Your comment has been added. If it does not immediately appear, it may have been flagged for moderation and should appear shortly."));
+}
+
+=head2 fetch_comment_cookie
+
+Creating a comment this way causes a cookie named "COMMENT_REMEMBORY" to be stored on the client to remember the client's name, email, and web site choice for the next comment.
+
+=cut
+
+my $comment_cookie;
+sub fetch_comment_cookie {
+    return $comment_cookie if defined $comment_cookie;
+
+    my %cookies = CGI::Cookie->fetch;
+    $comment_cookie 
+        = $cookies{'COMMENT_REMEMBORY'} ? $cookies{'COMMENT_REMEMBORY'} : '';
+
+    return $comment_cookie;
+}
+
+=head2 from_cookie
+
+Loads the name, email, and web site from the stored cookie.
+
+=cut
+
+sub from_cookie {
+    my $pos = shift;
+
+    if (Jifty->web->current_user->id) {
+        return Jifty->web->escape(
+               $pos == 0 ? Jifty->web->current_user->user_object->name
+             : $pos == 1 ? Jifty->config->framework('Web')->{'BaseURL'}
+             : $pos == 2 ? Jifty->web->current_user->user_object->email
+             :             ''
+        );
+    }
+
+    elsif (my $value = eval { fetch_comment_cookie()->value }) {
+        my @fields = split /\./, $value;
+
+        if (defined $fields[ $pos ]) {
+            return Jifty->web->escape(urlsafe_b64decode($fields[ $pos ]));
+        }
+        else {
+            return '';
+        }
+    }
+
+    else {
+        return '';
+    }
+}
+
+=head2 validate_title
+
+Make sure a title is set.
+
+=cut
+
+sub validate_title {
+    my $self = shift;
+    my $title = shift;
+
+    if (!$title || $title =~ /^\s*$/) {
+        return $self->validation_error(title => 'You must give a title.');
+    }
+
+    return $self->validation_ok('title');
+}
+
+#sub canonicalize_your_name {
+#    my $self = shift;
+#    my $your_name = shift;
+#
+#    if (!$your_name || $your_name =~ /^\s*$/ || $your_name =~ /\banonymous\b/i) {
+#        $self->canonicalization_note( your_name => 'Afraid to stand behind your words? Any malicious or evil comments by an Anonymous Coward (or anyone) will be unpublished.' );
+#        return 'Anonymous Coward';
+#    }
+#
+#    return $your_name;
+#}
+
+=head2 validate_web_site
+
+Make sure the web site given is valid.
+
+=cut
+
+sub validate_web_site {
+    my $self = shift;
+    my $web_site = shift;
+
+    if (!$web_site || $web_site =~ /^\s*$/) {
+        return $self->validation_ok('web_site');
+    }
+
+    unless ($web_site =~ /^$RE{URI}{HTTP}$/) {
+        return $self->validation_error(
+            web_site => 'This does not look like a proper URL. Make sure it starts with http:// or https://'
+        );
+    }
+
+    return $self->validation_ok('web_site');
+}
+
+=head2 validate_email
+
+Make sure the email given is valid.
+
+=cut
+
+sub validate_email {
+    my $self = shift;
+    my $email = shift;
+
+    if (!$email || $email =~ /^\s*$/) {
+        return $self->validation_ok('email');
+    }
+
+    unless ($email =~ /^$RE{Email}{Address}$/) {
+        return $self->validation_error(
+            email => 'This does not look like a proper e-mail address.',
+        );
+    }
+
+    return $self->validation_ok('email');
+}
+
+=head2 validate_body
+
+Checks to see if the scrubbed HTML is the same as the given HTML to see if it will be changed on save and reports that to the client.
+
+=cut
+
+sub validate_body {
+    my $self = shift;
+    my $body = shift;
+
+    if (!$body || $body =~ /^\s*$/) {
+        return $self->validation_error(body => 'You must type a comment.');
+    }
+
+    my $plugin = Jifty->find_plugin('Jifty::Plugin::Comment');
+    my $scrubber = $plugin->scrubber;
+    my $message  = $plugin->scrub_message;
+
+    my $clean_body = $scrubber->scrub($body);
+    if ($clean_body ne $body) {
+        return $self->validation_warning(body => Jifty->web->escape($message));
+    }
+
+    return $self->validation_ok('body');
+}
+
+=head1 AUTHOR
+
+Andrew Sterling Hanenkamp, C<< <hanenkamp at cpan.org> >>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2008 Boomer Consulting, Inc. All Rights Reserved.
+
+This program is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
+1;

Added: jifty/branches/jquery/lib/Jifty/Plugin/Comment/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/lib/Jifty/Plugin/Comment/Dispatcher.pm	Sun Mar 16 17:45:40 2008
@@ -0,0 +1,144 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Comment::Dispatcher;
+use Jifty::Dispatcher -base;
+
+use Scalar::Util qw/ blessed looks_like_number /;
+
+=head1 NAME
+
+Jifty::Plugin::Comment::Dispatcher - dispatcher for the comment plugin
+
+=head1 DESCRIPTION
+
+Handles the dispatch of the C<__comment> paths used by this plugin.
+
+=head1 METHODS
+
+=head2 setup_parent_object
+
+Called internally by the dispatcher rules to create the "parent" dispatcher argument from "comment_upon" or "parent_class" and "parent_id".
+
+=cut
+
+sub setup_parent_object() {
+    my $parent;
+    unless (get 'parent') {
+        my ($parent_class, $parent_id) = @_;
+        if (get 'comment_upon') {
+            my $comment_upon = get 'comment_upon';
+            ($parent_class, $parent_id) = $comment_upon =~ /^([\w:]+)-(\d)$/;
+        }
+        else {
+            $parent_class = get 'parent_class';
+            $parent_id    = get 'parent_id';
+        }
+
+        abort 404 unless $parent_class and $parent_id;
+        abort 500 unless $parent_class =~ /^[\w:]+$/;
+        abort 500 unless eval { $parent_class->isa('Jifty::Record') };
+        abort 500 unless looks_like_number($parent_id);
+
+        $parent = $parent_class->new;
+        $parent->load($parent_id);
+
+        set parent => $parent;
+    }
+
+    else {
+        $parent = get 'parent';
+    }
+
+    abort 500 unless eval { $parent->isa('Jifty::Record') };
+    abort 500 unless eval { $parent->can('comments') };
+    abort 404 unless eval { $parent->id };
+
+}
+
+=head1 RULES
+
+=head2 __comment/list
+
+Sets up the "parent" argument for the list template.
+
+=cut
+
+on '__comment/list' => run {
+    setup_parent_object();
+};
+
+=head2 __comment/add
+
+Set up the "parent" argument for the add template and set the "CreateComment" action into the "action" argument.
+
+=cut
+
+on '__comment/add' => run {
+    setup_parent_object();
+
+    my $parent = get 'parent';
+
+    my $action = Jifty->web->new_action( 
+        class => 'CreateComment',
+        moniker => 'add-comment-'.$parent->id,
+        arguments => {
+            parent_class => blessed $parent,
+            parent_id    => $parent->id,
+        },
+    );
+    $action->argument_value( title => get('title') || '')
+        unless $action->argument_value('title');
+    set action => $action;
+
+    show '/__comment/add';
+};
+
+=head2 __comment/display
+
+Sets up the "comment" argument and will update the status and published values of the comment if "update_status" or "update_published" are set, respectively.
+
+=cut
+
+on '__comment/display' => run {
+    my $id = get 'id';
+
+    my $comment = Jifty->app_class('Model', 'Comment')->new;
+    $comment->load($id);
+
+    if (get 'update_status') {
+        my $action = $comment->as_update_action;
+        $action->argument_value( status => get 'update_status' );
+        $action->run;
+
+        Jifty->web->response->result( $action->moniker => $action->result );
+    }
+
+    if (defined get 'update_published') {
+        my $action = $comment->as_update_action;
+        $action->argument_value( published => get 'update_published' );
+        $action->run;
+
+        Jifty->web->response->result( $action->moniker => $action->result );
+    }
+
+    set comment => $comment;
+};
+
+=head1 SEE ALSO
+
+L<Jifty::Dispatcher>
+
+=head1 AUTHOR
+
+Andrew Sterling Hanenkamp, C<< <hanenkamp at cpan.org> >>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2008 Boomer Consulting, Inc. All Rights Reserved.
+
+This program is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
+1;

Added: jifty/branches/jquery/lib/Jifty/Plugin/Comment/Mixin/Model/Commented.pm
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/lib/Jifty/Plugin/Comment/Mixin/Model/Commented.pm	Sun Mar 16 17:45:40 2008
@@ -0,0 +1,187 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Comment::Mixin::Model::Commented;
+use base qw/ Jifty::DBI::Record::Plugin /;
+
+our @EXPORT = qw( comments comment_record_class for_commenting );
+
+use Scalar::Util qw/ blessed /;
+
+use Jifty::DBI::Schema;
+use Jifty::Record schema {
+};
+
+=head1 NAME
+
+Jifty::Plugin::Comment::Mixin::Model::Commented - add comments to a model
+
+=head1 SYNOPSIS
+
+  package App::Model::Fooble;
+
+  use Jifty::DBI::Schema;
+  use App::Record schema {
+      column scribble => type is 'text';
+      column wobble => type is 'int';
+  };
+
+  use Jifty::Plugin::Comment::Mixin::Model::Commented;
+
+=head1 DESCRIPTION
+
+Add this mixin to a model if you'd like to attach comments to it. Comments can be used to allow users of your system to comment upon and discuss the record to which they are attached.
+
+=head1 METHODS
+
+=head2 import
+
+This method performs some rather devious magic to make everything work easily. It automatically generates an additional model for your application. This model will look something like this:
+
+  use strict;
+  use warnings;
+  
+  package App::Model::FoobleComment;
+  use Jifty::DBI::Schema;
+  
+  use Jifty::Record schema {
+      column commented_upon =>
+          references App::Model::Fooble,
+          label is 'Commented upon',
+          is mandatory,
+          is immutable,
+          ;
+  
+      column the_comment =>
+          references App::Model::Comment,
+          label is 'Comment',
+          is mandatory,
+          is immutable,
+          is distinct,
+          ;
+  };
+
+  App::Model::FoobleComment->add_trigger( before_access => sub {
+      my $self = shift;
+      my ($right, %args) = @_;
+
+      if ($right eq 'create') {
+          return 'allow' if $self->current_user->id;
+      }
+
+      if ($right eq 'read') {
+          return 'allow';
+      }
+
+      return $self->App::Model::FoobleComment::current_user_can(@_);
+  });
+  
+You will need to define an C<before_access> trigger for this class if you want it to be useful.
+
+=cut
+
+sub import {
+    my $my_class      = caller;
+    my $comment_class = $my_class.'Comment';
+    my $app_class     = Jifty->app_class;
+
+    eval "
+use strict;
+use warnings;
+
+package ${comment_class};
+use Jifty::DBI::Schema;
+
+use Jifty::Record schema {
+    column commented_upon =>
+        references ${my_class},
+        label is 'Commented upon',
+        is mandatory,
+        is immutable,
+        ;
+
+    column the_comment =>
+        references ${app_class}::Model::Comment,
+        label is 'Comment',
+        is mandatory,
+        is immutable,
+        is distinct,
+        ;
+};
+
+1;
+";
+
+    die "Failed to create comment link model ${comment_class}: $@" if $@;
+
+    Jifty->class_loader->_require_model_related_classes($comment_class);
+
+    my $comment_filename = $comment_class;
+    $comment_filename =~ s{::}{/}g;
+    $comment_filename .= '.pm';
+    $INC{ $comment_filename } = 'autogenerated';
+
+    goto &Jifty::DBI::Record::Plugin::import;
+}
+
+=head2 for_commenting
+
+Returns a value to be used with the comment views. It's basically just a string identifying the class name and ID of the record.
+
+=cut
+
+sub for_commenting {
+    my $self = shift;
+    return blessed($self) . '-' . $self->id;
+}
+
+=head2 comments
+
+Returns a collection of L<Jifty::Plugin::Comment::Model::Comment> objects that have been attached to the current record. (Actually, it returns the a collection of the local application class, e.g. C<App::Model::CommentCollection>.)
+
+=cut
+
+sub comments {
+    my $self = shift;
+
+    my $comments = Jifty->app_class('Model', 'CommentCollection')->new;
+    my $link_alias = $comments->join(
+        column1 => 'id',
+        table2  => $self->comment_record_class->table,
+        column2 => 'the_comment',
+    );
+
+    $comments->limit(
+        alias  => $link_alias,
+        column => 'commented_upon',
+        value  => $self->id,
+    );
+
+    return $comments;
+}
+
+=head2 comment_record_class
+
+This is the name of the linking class that was created during L</import>.
+
+=cut
+
+sub comment_record_class {
+    my $self = shift;
+    return (ref $self || $self).'Comment';
+}
+
+=head1 AUTHOR
+
+Andrew Sterling Hanenkamp C<< <hanenkamp at cpan.com> >>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Boomer Consulting, Inc. All Rights Reserved.
+
+This program is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
+1;
+

Added: jifty/branches/jquery/lib/Jifty/Plugin/Comment/Model/Comment.pm
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/lib/Jifty/Plugin/Comment/Model/Comment.pm	Sun Mar 16 17:45:40 2008
@@ -0,0 +1,321 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Comment::Model::Comment;
+use Jifty::DBI::Schema;
+
+use constant CLASS_UUID => '7B703CCA-544E-11DC-9227-2E96D84604BE';
+
+=head1 NAME
+
+Jifty::Plugin::Comment::Model::Comment - comments attached to anything
+
+=head1 SYNOPSIS
+
+  # to customize...
+  package App::Model::Comment;
+  use base qw/ Jifty::Plugin::Comment::Model::Comment /;
+
+  use Jifty::DBI::Schema;
+  use App::Record schema {
+      # Add a reference to the current user that creates this comment
+      column by_user =>
+          references App::Model::User,
+          ;
+  };
+
+  # make it so that any logged user can comment, but anyone can view, except
+  # that we don't really want everyone seeing all those personal bits...
+  sub current_user_can {
+      my $self = shift;
+      my ($right, %args) = @_;
+
+      if ($right eq 'create') {
+          return 1 if $self->current_user->id;
+      }
+
+      if ($right eq 'read') {
+          return $self->published
+              unless $args{column} =~ /^ 
+                  (?: email
+                    | status
+                    | ip_addr
+                    | http_refer
+                    | http_user_agent ) $/x;
+      }
+
+      # otherwise only superuser gets in
+      return $self->SUPER::current_user_can(@_);
+  }
+
+=head1 DESCRIPTION
+
+This model is the repository for all comments in your application, if you use the L<Jifty::Plugin::Comment> plugin.
+
+=head1 SCHEMA
+
+=head2 title
+
+This is the title of the comment.
+
+=head2 body
+
+This is the body of the comment.
+
+=head2 created_on
+
+This is the timestamp of when the comment was created.
+
+=head2 your_name
+
+This is the name the author of the comment has claimed.
+
+=head2 web_site
+
+This is the name of the web site the author claims as her own.
+
+=head2 email
+
+This is the email address the author is claiming.
+
+=head2 published
+
+This is a boolean flag indicating whether the comment should be shown or not when viewed.
+
+=head2 status
+
+This is a flag containing one of two values: "spam" or "ham". It indicates whether the comment has been evaluated as spam or not by L<Net::Akismet>.
+
+=head2 ip_addr
+
+This is the IP address of the remote client of the author that made the comment.
+
+=head2 http_referer
+
+This is the HTTP referer that was sent by the browser when the author made the comment.
+
+=head2 http_user_agent
+
+This is the HTTP user agent that was sent by the browser when the author made the comment.
+
+=cut
+
+use Jifty::Record schema {
+    column title =>
+        type is 'text',
+        label is 'Title',
+        is mandatory,
+        ;
+
+    column body =>
+        type is 'text',
+        label is 'Body',
+        is mandatory,
+        render as 'Textarea',
+        ;
+
+    column created_on =>
+        type is 'timestamp',
+        label is 'Created on',
+        filters are qw/ Jifty::DBI::Filter::DateTime /,
+        ;
+
+    column your_name =>
+        type is 'text',
+        label is 'Your name',
+        is mandatory,
+        ;
+
+    column web_site =>
+        type is 'text',
+        label is 'Web site',
+        ;
+
+    column email =>
+        type is 'text',
+        label is 'Email address',
+        ;
+
+    column published =>
+        type is 'boolean',
+        label is 'Published?',
+        is mandatory,
+        default is 1,
+        ;
+
+    column status =>
+        type is 'varchar(4)',
+        label is 'Status',
+        valid_values are qw/ spam ham /,
+        default is 'ham',
+        ;
+
+    column ip_addr =>
+        type is 'text',
+        label is 'IP Address',
+        ;
+
+    column http_referer =>
+        type is 'text',
+        label is 'HTTP Referer',
+        ;
+
+    column http_user_agent =>
+        type is 'text',
+        label is 'HTTP User Agent',
+        ;
+};
+
+use DateTime;
+use HTML::Scrubber;
+
+=head1 METHODS
+
+=head2 table
+
+Returns the database table name for the comments table.
+
+=cut
+
+sub table { 'comment_comments' }
+
+=head2 before_create
+
+It is assumed that your comments will be made available for create with very little restriction. This trigger is used to perform aggressive cleanup on the data stored and will attempt to check to see if the comment is spam by using L<Net::Akismet>.
+
+=cut
+
+sub before_create {
+    my $self = shift;
+    my $args = shift;
+
+    # Clean up stuff added by Jifty::Plugin::Comment::Action::CreateComment
+    delete $args->{parent_class};
+    delete $args->{parent_id};
+
+    my $plugin   = Jifty->find_plugin('Jifty::Plugin::Comment');
+    my $scrubber = $plugin->scrubber;
+
+    # Store safe fields
+    $args->{'title'}           = Jifty->web->escape($args->{'title'});
+    $args->{'your_name'}       = Jifty->web->escape($args->{'your_name'});
+    $args->{'web_site'}        = Jifty->web->escape($args->{'web_site'});
+    $args->{'email'}           = Jifty->web->escape($args->{'email'});
+    $args->{'body'}            = $scrubber->scrub($args->{'body'});
+
+    $args->{'created_on'}      = DateTime->now;
+
+    $args->{'ip_addr'}         = $ENV{'REMOTE_ADDR'};
+    $args->{'http_user_agent'} = $ENV{'HTTP_USER_AGENT'};
+    $args->{'http_referer'}    = $ENV{'HTTP_REFERER'};
+
+    # Prep for Akismet check or stop
+    my $akismet = $plugin->akismet or return 1;
+
+    # Check to see if it's classified as spam
+    my $verdict = $akismet->check(
+        USER_IP              => $args->{'ip_addr'},
+        USER_AGENT           => $args->{'http_user_agent'},
+        REFERER              => $args->{'http_referer'},
+        COMMENT_CONTENT      => $args->{'title'}."\n\n".$args->{'body'},
+        COMMENT_AUTHOR       => $args->{'your_name'},
+        COMMENT_AUTHOR_EMAIL => $args->{'email'},
+        COMMENT_AUTHOR_URL   => $args->{'web_site'},
+    );
+
+    # I have no idea what it is... mark it spam just in case...
+    # TODO the default no verdict action should configurable
+    if (!$verdict) {
+        $args->{published} = 0;
+        $args->{status}    = 'spam';
+
+        warn "Failed to determine whether new comment is spam or not.";
+        return 1;
+    }
+
+    # Naughty, naughty... mark as spam
+    if ($verdict eq 'true') {
+        warn "A new comment is detected as spam.";
+
+        $args->{published} = 0;
+        $args->{status}    = 'spam';
+
+        return 1;
+    }
+
+    # Excellent, post that ham
+    else {
+        $args->{published} = 1;
+        $args->{status}    = 'ham';
+
+        return 1;
+    }
+}
+
+=head2 before_set_status
+
+This trigger is called when changing the status of the message. If L<Net::Akismet> is in use, this trigger will notify Akismet that this message is being marked as spam or as ham, depending upon the new value.
+
+=cut
+
+sub before_set_status {
+    my $self = shift;
+    my $args = shift;
+
+    my $plugin  = Jifty->find_plugin('Jifty::Plugin::Comment');
+    my $akismet = $plugin->akismet or return 1;
+
+    my %akismet_report = (
+        USER_IP              => $self->ip_addr,
+        USER_AGENT           => $self->http_user_agent,
+        REFERER              => $self->http_referer,
+        COMMENT_CONTENT      => $self->title."\n\n".$self->body,
+        COMMENT_AUTHOR       => $self->your_name,
+        COMMENT_AUTHOR_EMAIL => $self->email,
+        COMMENT_AUTHOR_URL   => $self->web_site,
+    );
+
+    if ($self->status eq 'spam' && $args->{value} eq 'ham') {
+        if ($akismet->ham( %akismet_report )) {
+            Jifty->log->info("Reported that comment ".$self->id." is HAM to Akismet.");
+        }
+        else {
+            # Not the end of the world, just that Akismet doesn't know...
+            Jifty->log->info("FAILED to report that comment ".$self->id." is HAM to Akismet.");
+        }
+    }
+
+    elsif ($self->status eq 'ham' && $args->{value} eq 'spam') {
+        if ($akismet->spam( %akismet_report )) {
+            Jifty->log->info("Reported that comment ".$self->id." is SPAM to Akismet.");
+        }
+        else {
+            # Not the end of the world, just that Akismet doesn't know...
+            Jifty->log->info("FAILED to report that comment ".$self->id." is SPAM to Akismet.");
+        }
+    }
+
+    return 1;
+}
+
+=head2 current_user_can
+
+This method is not actually implemented by this class, but you will either want to implementt this method in your application or add a C<before_access> trigger that grants access. Otherwise, your comments won't be very interesting to anyone but a superuser.
+
+See the L</SYNOPSIS> for a recommended implementation.
+
+=head1 AUTHOR
+
+Andrew Sterling Hanenkamp C<< <hanenkamp at cpan.org> >>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Boomer Consulting, Inc. All Rights Reserved.
+
+This program is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
+1;
+
+

Added: jifty/branches/jquery/lib/Jifty/Plugin/Comment/Notification/CommentNeedsModeration.pm
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/lib/Jifty/Plugin/Comment/Notification/CommentNeedsModeration.pm	Sun Mar 16 17:45:40 2008
@@ -0,0 +1,124 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Comment::Notification::CommentNeedsModeration;
+use base qw/ Jifty::Notification /;
+
+__PACKAGE__->mk_accessors(qw/ comment parent /);
+
+=head1 NAME
+
+Jifty::Plugin::Comment::Notification::CommentNeedsModeration - new comments made, but not published
+
+=head1 SYNOPSIS
+
+To activate this notification, you must override the notification in your application.
+
+  use strict;
+  use warnings;
+
+  package MyApp::Notification::CommentNeedsModeration;
+  use base qw/ Jifty::Plugin::Comment::Notification::CommentNeedsModeration /;
+
+  sub setup {
+      my $self = shift;
+
+      # Limit to users that have a "moderator" column set to 1
+      my $users = MyApp::Model::UserCollection->new;
+      $users->limit( column => 'moderator', value => 1 );
+      $self->to_list(@{ $users->items_array_ref });
+
+      $self->SUPER::setup(@_);
+  }
+
+  sub url {
+      my $self = shift;
+      return Jifty->config->framework('Web')->{'BaseURL'}
+          . $self->parent->permalink
+          . '#comment-'.$self->comment->id;
+  }
+
+  1;
+
+=head1 DESCRIPTION
+
+This notificaiton (when properly configured) is sent out to any who need to know when a comment has been created, but not published because L<Net::Akismet> has marked it as spam.
+
+=head1 METHODS
+
+=head2 setup
+
+This method sets up the notification. This method should be overridden to setup L<Jifty::Notification/to_list> to select who will receive this message. See the L</SYNOPSIS>.
+
+=cut
+
+sub setup {
+    my $self = shift;
+
+    my $appname = Jifty->config->framework('ApplicationName');
+    my $comment = $self->comment;
+
+    my $from = $comment->your_name || 'Anonymous Coward';
+    $from .= ' <'.$comment->email.'>' if $comment->email;
+    $from .= ' ('.$comment->web_site.')'      if $comment->web_site;
+
+    my $url = $self->url;
+
+    $self->subject(_("[%1] Moderate comment: %2", $appname, $comment->title));
+    $self->body(_(q{
+The following comment has not been published. If you would like to publish it, please visit the link below and click on the "publish" link. If it has been marked as spam and should not have been you should also click on the "mark as ham" link.
+
+View Comment: %1
+
+On Post: %2
+Subject: %3
+From: %4
+Date: %5
+
+%6
+}, 
+        $url, 
+        $self->parent->title,
+        $comment->title, 
+        $from, 
+        $comment->created_on->strftime('%A, %B %d, %Y @ %H:%M%P'), 
+        $comment->body
+    ));
+}
+
+=head2 comment
+
+This will contain the L<Jifty::Plugin::Comment::Model::Comment> that has been published.
+
+=head2 parent
+
+This will contain the object that the comment has been attached to.
+
+=head2 url
+
+THis returns the URL that the message will link to. This should be overridden to provide application-specific URLs. The default implementation returns the BaseURL setting for the application.
+
+=cut
+
+sub url {
+    my $self = shift;
+    return Jifty->config->framework('Web')->{'BaseURL'};
+}
+
+=head1 SEE ALSO
+
+L<Jifty::Notification>, L<Jifty::Plugin::Comment::Notification::CommentPublished>
+
+=head1 AUTHOR
+
+Andrew Sterling Hanenkamp, C<< <hanenkamp at cpan.org> >>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2008 Boomer Consulting, Inc. All Rights Reserved.
+
+This program is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
+1;

Added: jifty/branches/jquery/lib/Jifty/Plugin/Comment/Notification/CommentPublished.pm
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/lib/Jifty/Plugin/Comment/Notification/CommentPublished.pm	Sun Mar 16 17:45:40 2008
@@ -0,0 +1,120 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Comment::Notification::CommentPublished;
+use base qw/ Jifty::Notification /;
+
+__PACKAGE__->mk_accessors(qw/ comment parent /);
+
+=head1 NAME
+
+Jifty::Plugin::Comment::Notification::CommentPublished - new comments made
+
+=head1 SYNOPSIS
+
+To activate this notification, you must override the notification in your application.
+
+  use strict;
+  use warnings;
+
+  package MyApp::Notification::CommentPublished;
+  use base qw/ Jifty::Plugin::Comment::Notification::CommentPublished /;
+
+  sub setup {
+      my $self = shift;
+
+      # Send to the author of the post
+      $self->to_list($self->parent->author);
+
+      $self->SUPER::setup(@_);
+  }
+
+  sub url {
+      my $self = shift;
+      return Jifty->config->framework('Web')->{'BaseURL'}
+          . $self->parent->permalink
+          . '#comment-'.$self->comment->id;
+  }
+
+  1;
+
+=head1 DESCRIPTION
+
+This notification (when properly configured) is sent out to any who need to know when a comment has been published.
+
+=head1 METHODS
+
+=head2 setup
+
+This method sets up the notification. This method should be overridden to setup L<Jifty::Notification/to_list> to select who will receive this message. See the L</SYNOPSIS>.
+
+=cut
+
+sub setup {
+    my $self = shift;
+
+    my $appname = Jifty->config->framework('ApplicationName');
+    my $comment = $self->comment;
+
+    my $from = $comment->your_name || 'Anonymous Coward';
+    $from .= ' <'.$comment->email.'>' if $comment->email;
+    $from .= ' ('.$comment->web_site.')'      if $comment->web_site;
+
+    my $url = $self->url;
+
+    $self->subject(_("[%1] New comment: %2", $appname, $comment->title));
+    $self->body(_("
+View Comment: %1
+
+On Post: %2
+Subject: %3
+From: %4
+Date: %5
+
+%6
+", 
+        $url,
+        $self->parent->title,
+        $comment->title, 
+        $from, 
+        $comment->created_on->strftime('%A, %B %d, %Y @ %H:%M%P'), 
+        $comment->body
+    ));
+}
+
+=head2 comment
+
+This will contain the L<Jifty::Plugin::Comment::Model::Comment> that has been published.
+
+=head2 parent
+
+This will contain the object that the comment has been attached to.
+
+=head2 url
+
+This returns the URL that the message will link to. This should be overridden to provide application-specific URLs. The default implementation returns the BaseURL setting for the application.
+
+=cut
+
+sub url {
+    my $self = shift;
+    return Jifty->config->framework('Web')->{'BaseURL'};
+}
+
+=head1 SEE ALSO
+
+L<Jifty::Notification>, L<Jifty::Plugin::Comment::Notification::CommentNeedsModeration>
+
+=head1 AUTHOR
+
+Andrew Sterling Hanenkamp, C<< <hanenkamp at cpan.org> >>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2008 Boomer Consulting, Inc. All Rights Reserved.
+
+This program is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
+1;

Added: jifty/branches/jquery/lib/Jifty/Plugin/Comment/View.pm
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/lib/Jifty/Plugin/Comment/View.pm	Sun Mar 16 17:45:40 2008
@@ -0,0 +1,333 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Comment::View;
+use Jifty::View::Declare -base;
+
+use Jifty::DateTime;
+
+=head1 NAME
+
+Jifty::Plugin::Comment::View - the templates for the comment plugin
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=head2 scrub_html
+
+This is a utility used internally for cleaning up the input which might come from a malicious source.
+
+=cut
+
+sub scrub_html($) {
+    my $text = shift;
+
+    my $plugin = Jifty->find_plugin('Jifty::Plugin::Comment');
+    return $plugin->scrubber->scrub($text);
+}
+
+=head1 TEMPLATES
+
+=head2 __comment/view
+
+This displays a single comment in a page.
+
+=cut
+
+template '__comment/view' => page {
+    my $comment = get 'comment';
+
+    { title is _( $comment->title ); }
+
+    div {
+        { class is 'column span-21 first' }
+
+        render_region
+            name => 'comment-'.$comment->id,
+            path => '/__comment/display',
+            defaults => {
+                id  => $comment->id,
+                top => 1,
+            },
+            ;
+    };
+
+    show '/advertisement';
+};
+
+=head2 __comment/display
+
+Display a comment in a page region.
+
+=cut
+
+template '__comment/display' => sub {
+    my $comment = get 'comment';
+    my $top     = get 'top';
+
+    div {
+        my $class = 'comment';
+        $class .= $comment->status eq 'ham' ? ' ham' : ' spam'
+            if Jifty->web->current_user->id;
+        $class .= $comment->published ? ' published' : ' unpublished';
+
+        { class is $class }
+
+        div {
+            { class is 'actions' }
+
+            if (Jifty->web->current_user->id) {
+
+                for my $status (qw( ham spam )) {
+                    if ($comment->status ne $status) {
+                        hyperlink
+                            label => _('mark as %1', $status),
+                            onclick => {
+                                args  => {
+                                    update_status => $status,
+                                },
+                            },
+                            ;
+                    }
+                }
+
+                for my $published (0, 1) {
+                    if ($comment->published ne $published) {
+                        hyperlink
+                            label => _($published ? 'publish' : 'unpublish'),
+                            onclick => {
+                                args  => {
+                                    update_published => $published,
+                                },
+                            },
+                            ;
+                    }
+                }
+            }
+
+            '';
+        };
+
+        unless ($top) {
+            h5 { 
+                a {
+                    attr { name => 'comment-'.$comment->id };
+                    $comment->title 
+                };
+            };
+        }
+
+        div {
+            { class is 'comment-info' }
+
+            my $poster = $comment->your_name || 'Anonymous Coward';
+            $poster = Jifty->web->escape($poster);
+            $poster = qq{<a href="@{[$comment->web_site]}">$poster</a>}
+                if $comment->web_site;
+
+            my $created_on = Jifty::DateTime->now;
+
+            p { 
+                outs_raw _('By %1 %2', 
+                    $poster, 
+                    $created_on->strftime('%A, %B %d, %Y @ %H:%M%P')
+                ) 
+            };
+        };
+
+        div {
+            outs_raw scrub_html($comment->body);
+        };
+
+    };
+};
+
+=head2 __comment/add
+
+This presents the form for adding a new comment.
+
+=cut
+
+template '__comment/add' => sub {
+    my $collapsed = get 'collapsed';
+    my $region    = get 'region';
+
+    if ($collapsed) {
+        p {
+            hyperlink
+                label => _('Add a comment'),
+                onclick => {
+                    refresh_self => 1,
+                    args => { collapsed => 0 },
+                },
+                ;
+        };
+    }
+
+    else {
+        my $action = get 'action';
+
+        if (get 'preview') {
+            div {
+                { class is 'preview comment' }
+
+                h5 { $action->argument_value('title') || 'No Title' };
+
+                div {
+                    { class is 'comment-info' }
+
+                    my $poster = $action->argument_value('your_name') 
+                              || 'Anonymous Coward';
+                    $poster = Jifty->web->escape($poster);
+                    $poster = qq{<a href="@{[$action->argument_value('web_site')]}">$poster</a>}
+                        if $action->argument_value('web_site');
+
+                    my $created_on = Jifty::DateTime->now;
+
+                    p { 
+                        outs_raw _('By %1 %2', 
+                            $poster, 
+                            $created_on->strftime('%A, %B %d, %Y @ %H:%M%P')
+                        ) 
+                    };
+                };
+
+                div {
+                    my $body = $action->argument_value('body')
+                            || 'No Body';
+
+                    outs_raw scrub_html($body);
+                };
+            };
+        };
+
+        div {
+            { class is 'edit comment' }
+
+            form {
+                render_action $action;
+
+                div {
+                    { class is 'submit-buttons' }
+
+                    form_submit
+                        label => _('Preview'),
+                        name => 'op',
+                        class => 'first',
+                        onclick => {
+                            refresh_self => 1,
+                            submit => { 
+                                action => $action, 
+                                arguments => { submit => 0 },
+                            },
+                            args => { preview => 1 },
+                        },
+                        ;
+
+                    if (get 'preview') {
+                        form_submit
+                            label => _('Submit'),
+                            onclick => [ 
+                                {
+                                    refresh_self => 1,
+                                    submit => {
+                                        action => $action,
+                                        arguments => { submit => 1 },
+                                    },
+                                    args => {
+                                        preview => 0,
+                                        collapsed => 1,
+                                    },
+                                },
+                                {
+                                    element => $region->parent->get_element('div.list'),
+                                    append  => '/__comment/display',
+                                    args    => {
+                                        id  => { result => $action, name => 'id' },
+                                        top => 0,
+                                    },
+                                },
+                            ],
+                            ;
+                    }
+                };
+            };
+        };
+    }
+};
+
+=head2 __comment/list
+
+This presents a list of comments attached to a particular comment and the form for adding one more.
+
+=cut
+
+template '__comment/list' => sub {
+    my $parent   = get 'parent';
+    my $title    = get 'initial_title';
+    my $comments = $parent->comments;
+
+    if (!Jifty->web->current_user->id) {
+        $comments->limit(
+            column => 'status',
+            value  => 'ham',
+        );
+    }
+
+    div {
+        { class is 'list' }
+
+        if ($comments->count) {
+            while (my $comment = $comments->next) {
+                render_region
+                    name => 'comment-'.$comment->id,
+                    path => '/__comment/display',
+                    defaults => {
+                        id  => $comment->id,
+                        top => 0,
+                    },
+                    ;
+            }
+        }
+
+        else {
+            p {
+                { class is 'none' }
+
+                _('No one has made a comment yet.');
+            };
+        }
+    };
+
+    unless (get 'no_add') {
+        render_region
+            name     => 'comment-add-'.$parent->id,
+            path     => '/__comment/add',
+            defaults => {
+                parent_class => ref $parent,
+                parent_id    => $parent->id,
+                collapsed    => 1,
+                title        => $title,
+            },
+            ;
+    }
+};
+
+=head1 SEE ALSO
+
+L<Jifty::View::Declare>
+
+=head1 AUTHOR
+
+Andrew Sterling Hanenkamp, C<< <hanenkamp at cpan.org> >>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2008 Boomer Consulting, Inc. All Rights Reserved.
+
+This program is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
+1;

Modified: jifty/branches/jquery/lib/Jifty/Plugin/CompressedCSSandJS.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Plugin/CompressedCSSandJS.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Plugin/CompressedCSSandJS.pm	Sun Mar 16 17:45:40 2008
@@ -237,18 +237,19 @@
     }
 
     my $obj = Jifty::CAS->retrieve('ccjs', $key);
+    my $compression = '';
+    $compression = 'gzip' if $obj->metadata->{deflate}
+      && Jifty::View::Static::Handler->client_accepts_gzipped_content;
+
     Jifty->handler->apache->content_type($obj->metadata->{content_type});
-    Jifty->handler->apache->header_out( 'Expires' => HTTP::Date::time2str( time + 31536000 ) );
+    Jifty::View::Static::Handler->send_http_header($compression, length($obj->content));
 
-    if ($obj->metadata->{deflate} && Jifty::View::Static::Handler->client_accepts_gzipped_content ) {
+    if ( $compression ) {
         Jifty->log->debug("Sending gzipped squished $name");
-        Jifty->handler->apache->header_out( "Content-Encoding" => "gzip" );
-        Jifty->handler->apache->send_http_header();
         binmode STDOUT;
         print $obj->content_deflated;
     } else {
         Jifty->log->debug("Sending squished $name");
-        Jifty->handler->apache->send_http_header();
         print $obj->content;
     }
 }

Modified: jifty/branches/jquery/lib/Jifty/Plugin/Monitoring.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Plugin/Monitoring.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Plugin/Monitoring.pm	Sun Mar 16 17:45:40 2008
@@ -62,7 +62,7 @@
                  month months
                  year years
 
-                 data_point timer/;
+                 data_point timer previous/;
 
 BEGIN {
     for my $time (qw/minute hour day week month year/) {
@@ -113,7 +113,7 @@
     $self->add_monitor(@_);
 }
 
-=head2 data_point NAME, VALUE [, CATEGORY]
+=head2 data_point [CATEGORY,] NAME, VALUE
 
 Records a data point, associating C<NAME> to C<VALUE> at the current
 time.  C<CATEGORY> defaults to the name of the monitor that the data
@@ -137,6 +137,31 @@
     );
 }
 
+=head2 previous [CATEGORY,] NAME
+
+Returns the most recent valeu for the data point of the given C<NAME>
+and C<CATEGORY>.  C<CATEGORY> defaults to the name of the current
+monitor.
+
+=cut
+
+sub previous {
+    my ($self) = Jifty->find_plugin("Jifty::Plugin::Monitoring");
+    $self ||= $Jifty::Plugin::Monitoring::self;
+
+    my $category = @_ == 2 ? shift : $self->current_monitor->{name};
+    my ($name) = @_;    
+
+    my $data = Jifty::Plugin::Monitoring::Model::MonitoredDataPointCollection->new();
+    $data->limit( column => 'category', value => $category );
+    $data->limit( column => 'sample_name', value => $name );
+    $data->limit( column => 'sampled_at', operator => '<', value => $self->now );
+    $data->set_page_info(per_page => 1);
+    $data->order_by(column => 'sampled_at', order => 'DESC');
+    my $row = $data->first;
+    return $row ? $row->value : undef;
+}
+
 =head2 timer MECH, URL
 
 Uses L<Time::HiRes> to time how long it takes the given
@@ -236,7 +261,7 @@
 
     my $unit = $self->monitors->{$name}->{unit};
     my $now = Jifty::DateTime->now->truncate( to => $unit );
-    warn "No last run time for monitor $name; inserting $now\n";
+    Jifty->log->warn("No last run time for monitor $name; inserting $now");
     $last->set_last_run($now);
     return $last;
 }
@@ -282,17 +307,15 @@
         my $last = $self->last_run($name);
         my %monitor = %{$self->monitors->{$name}};
         my $next = $last->last_run->add( $monitor{unit}."s" => $monitor{count} );
-        warn "For monitor $name, next $next, now $now\n";
         next unless $now >= $next;
-        warn "Cron not being run often enough: we skipped a '$name'!\n"
+        Jifty->log->warn("Cron not being run often enough: we skipped a '$name'!")
           if $now >= $next->add( $monitor{unit}."s" => $monitor{count} );
-        warn "Running monitor $name\n";
         $self->current_monitor(\%monitor);
         eval {
             $monitor{sub}->($self);
         };
         if (my $error = $@) {
-            warn "Error running monitor $name: $error\n";
+            Jifty->log->warn("Error running monitor $name: $error");
         } else {
             $last->set_last_run($now);
         }
@@ -300,11 +323,28 @@
     $self->current_monitor(undef);
 }
 
+=head2 lock
+
+Attempt to determine if there are other monitoring processes running.
+If there are, we return false.  This keeps a long-running monitor from
+making later jobs pile up.
+
+=cut
+
 sub lock {
     my $self = shift;
-    return if -e $self->lockfile;
+    if (-e $self->lockfile) {
+        my ($pid) = do {local @ARGV = ($self->lockfile); <>};
+        if (kill 0, $pid) {
+            Jifty->log->warn("Monitor PID $pid still running");
+            return 0;
+        } else {
+            Jifty->log->warn("Stale PID file @{[$self->lockfile]}; removing");
+            unlink($self->lockfile);
+        }
+    }
     unless (open PID, ">", $self->lockfile) {
-        warn "Can't open lockfile @{[$self->lockfile]}: $!";
+        Jifty->log->warn("Can't open lockfile @{[$self->lockfile]}: $!");
         return 0;
     }
     print PID $$;
@@ -313,6 +353,12 @@
     return 1;
 }
 
+=head2 DESTROY
+
+On destruction, remove the lockfile.
+
+=cut
+
 sub DESTROY {
     my $self = shift;
     unlink $self->lockfile if $self->has_lock;

Modified: jifty/branches/jquery/lib/Jifty/Plugin/OAuth.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Plugin/OAuth.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Plugin/OAuth.pm	Sun Mar 16 17:45:40 2008
@@ -4,23 +4,109 @@
 
 use base qw/Jifty::Plugin/;
 
-our $VERSION = 0.01;
+our $VERSION = '0.01';
+
+sub init {
+    Jifty::CurrentUser->mk_accessors(qw(is_oauthed oauth_token));
+
+    Jifty::Record->add_trigger(before_access => sub {
+        my $record = shift;
+        my $right  = shift;
+
+        # not oauthed, so use default
+        $record->current_user->is_oauthed
+            or return 'ignore';
+        my $token = $record->current_user->oauth_token;
+
+        # OAuthed users have no read restrictions, so use default
+        return 'ignore' if $right eq 'read';
+
+        # token gives write access, so use default
+        return 'ignore' if $token->__value('can_write');
+
+        # we have been forbidden from writing!
+        Jifty->log->error("Unable to $right " . ref($record) . " " . ($record->id||'new') . " because the OAuth access token does not allow it.");
+        return 'deny';
+    });
+
+    for my $type (qw/create set delete/) {
+        Jifty::DBI::Record->add_trigger(
+            abortable => 1,
+            name      => "before_$type",
+            callback  => sub {
+                my $record = shift;
+
+                # not a Jifty::Object, so allow write
+                $record->can('current_user')
+                    or return 1;
+
+                # not oauthed, so allow write
+                $record->current_user->is_oauthed
+                    or return 1;
+
+                my $token = $record->current_user->oauth_token;
+
+                # token gives write access, so allow write
+                return 1 if $token->__value('can_write');
+
+                # we have been forbidden from writing!
+                Jifty->log->debug("Unable to $type " . ref($record) . " " . ($record->id||'new') . " because the OAuth access token does not allow it.");
+                my $ret = Class::ReturnValue->new;
+                $ret->as_array(0, "Your OAuth access token denies you write access.");
+                $ret->as_error(
+                    errno => 1,
+                    message => 'Your OAuth access token denies you write access.',
+                );
+                return $ret->return_value;
+            },
+        );
+    }
+}
 
 =head1 NAME
 
-Jifty::Plugin::OAuth
+Jifty::Plugin::OAuth - secure API authentication
 
 =head1 DESCRIPTION
 
-A OAuth web services API for your Jifty app.
+A OAuth web services API for your Jifty app. Other applications may have secure
+and limited access to your users' data.
+
+This plugin adds an C</oauth> set of URLs to your application, listed below. It
+also adds C<is_oauthed> and C<oauth_token> to L<Jifty::CurrentUser>, so you may
+have additional restrictions on OAuth access (such as forbidding OAuthed users
+to change users' passwords).
+
+=head2 /oauth
+
+This lists some basic information about OAuth, and where to find more. It also
+tells consumers how they may gain OAuth-ability for your site.
+
+=head2 /oauth/request_token
+
+The URL that consumers POST to get a request token
+
+=head2 /oauth/authorize
+
+The URL at which users authorize request tokens
+
+=head2 /oauth/authorized
+
+After authorizing or denying a request token, users are directed here before
+going back to the consumer's site.
+
+=head2 /oauth/access_token
+
+The URL that consumers POST to trade an authorized request token for an access
+token
 
 =head1 WARNING
 
-This plugin is not yet complete. DO NOT USE IT.
+This plugin is beta. Please let us know if there are any issues with it.
 
 =head1 USAGE
 
-Add the following to your site_config.yml
+Add the following to your config:
 
  framework:
    Plugins:
@@ -66,9 +152,12 @@
 You must not allow public access to C</oauth/authorize>. C</oauth/authorize>
 depends on having the user be logged in.
 
+You should allow public access to C</oauth>. This has some information for
+consumers.
+
 There is currently no way for consumers to add themselves. This might change in
-the future, but it would be a nondefault configuration. Consumers must
-contact you and provide you with the following data:
+the future, with an OAuth extension. Consumers must contact you and provide you
+with the following data:
 
 =over 4
 
@@ -145,6 +234,14 @@
 in a very secure manner. For example, a replay attack (an eavesdropper repeats
 a request made by a legitimate consumer) is actively defended against.
 
+=head1 METHODS
+
+=head2 init
+
+This adds an is_oauthed accessor to L<Jifty::CurrentUser>. It also establishes
+a trigger in L<Jifty::Record> so that only OAuthed consumers with write access
+can do anything other than read.
+
 =head1 SEE ALSO
 
 L<Net::OAuth::Request>, L<http://oauth.net/>

Modified: jifty/branches/jquery/lib/Jifty/Plugin/OAuth/Action/AuthorizeRequestToken.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Plugin/OAuth/Action/AuthorizeRequestToken.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Plugin/OAuth/Action/AuthorizeRequestToken.pm	Sun Mar 16 17:45:40 2008
@@ -19,11 +19,29 @@
         ajax validates;
 
     param 'authorize',
+        render as 'select',
         valid_values are qw(allow deny);
 
     param 'callback',
         render as 'hidden';
 
+    param 'use_limit',
+        label is 'Use limit',
+        hints are 'How long should the site have access?',
+        render as 'select',
+        default is '1 hour',
+        valid_values are (
+            '5 minutes',
+            '1 hour',
+            '1 day',
+            '1 week',
+        );
+
+    param 'can_write',
+        label is 'Write access?',
+        hints are 'Should the site be allowed to update your data? (unchecking restricts to read-only)',
+        render as 'checkbox',
+        default is 0;
 };
 
 =head2 validate_token
@@ -39,7 +57,7 @@
     my $request_token = Jifty::Plugin::OAuth::Model::RequestToken->new(current_user => Jifty::CurrentUser->superuser);
     $request_token->load_by_cols(
         token => $token,
-        authorized => '',
+        authorized => 0,
     );
 
     return $self->validation_error(token => "I don't know of that request token.") unless $request_token->id;
@@ -68,19 +86,50 @@
 
     $self->result->content(token_obj => $token);
     $self->result->content(token     => $token->token);
-    $self->result->content(callback  => $self->argument_value('callback'));
+
+    for (qw/callback use_limit can_write/) {
+        $self->result->content($_ => $self->argument_value($_));
+    }
 
     if ($self->argument_value('authorize') eq 'allow') {
-        $token->set_authorized('t');
-        $self->result->message("Allowing " . $token->consumer->name . " to access your stuff.");
+        $token->set_authorized(1);
+        $token->set_access_token_restrictions({
+            can_write => $self->argument_value('can_write'),
+            use_limit => $self->inflate_use_limit,
+        });
+
+        my $right = $self->argument_value('can_write') ? "read and write" : "read";
+
+        $self->result->message("Allowing " . $token->consumer->name . " to $right your data for ". $self->argument_value('use_limit') .".");
     }
     else {
         $token->delete;
-        $self->result->message("Denying " . $token->consumer->name . " the right to access your stuff.");
+        $self->result->message("Denying " . $token->consumer->name . " the right to access your data.");
     }
 
     return 1;
 }
 
+=head2 inflate_use_limit -> DateTime
+
+Takes the use_limit argument and inflates it to a DateTime object representing
+when the access token will expire. It expects the input to be of the form
+"number_of_periods period_length", so "5 minutes", "1 hour", etc.
+
+=cut
+
+sub inflate_use_limit {
+    my $self      = shift;
+    my $use_limit = $self->argument_value('use_limit');
+
+    my ($periods, $length) = $use_limit =~ m{^(\d+)\s+(\w+)$}
+        or die "AuthorizeRequestToken->inflate_use_limit failed to parse input $use_limit";
+
+    # DateTime::Duration accepts only plurals
+    $length .= 's' if $periods == 1;
+
+    return DateTime->now->add($length => $periods);
+}
+
 1;
 

Modified: jifty/branches/jquery/lib/Jifty/Plugin/OAuth/Dispatcher.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Plugin/OAuth/Dispatcher.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Plugin/OAuth/Dispatcher.pm	Sun Mar 16 17:45:40 2008
@@ -7,7 +7,6 @@
 use Net::OAuth::RequestTokenRequest;
 use Net::OAuth::AccessTokenRequest;
 use Net::OAuth::ProtectedResourceRequest;
-use Crypt::OpenSSL::RSA;
 use URI::Escape 'uri_unescape';
 
 on     POST '/oauth/request_token' => \&request_token;
@@ -16,6 +15,9 @@
 on     POST '/oauth/access_token'  => \&access_token;
 on          '/oauth/authorized'    => run { redirect '/oauth/authorize' };
 
+on     GET  '/oauth/request_token' => \&invalid_method;
+on     GET  '/oauth/access_token'  => \&invalid_method;
+
 before '*' => \&try_oauth;
 
 =head2 abortmsg CODE, MSG
@@ -28,11 +30,11 @@
 sub abortmsg {
     my ($code, $msg) = @_;
     if ($code) {
-        Jifty->log->debug("$code for ".Jifty->web->request->path.":" . $msg) if defined($msg);
+        Jifty->log->debug("$code for ".Jifty->web->request->path.": $msg") if defined($msg);
         abort($code);
     }
     elsif (defined $msg) {
-        Jifty->log->debug("OAuth denied for ".Jifty->web->request->path.":" . $msg);
+        Jifty->log->debug("OAuth denied for ".Jifty->web->request->path.": $msg");
     }
 }
 
@@ -100,7 +102,7 @@
 
 sub authorize {
     my @params = qw/token callback/;
-    abortmsg(403, "Cannot authorize tokens as an OAuthed user") if Jifty->handler->stash->{oauth};
+    abortmsg(403, "Cannot authorize tokens as an OAuthed user") if Jifty->web->current_user->is_oauthed;
 
     set no_abort => 1;
     my %oauth_params = get_parameters(@params);
@@ -111,7 +113,7 @@
 
     if ($oauth_params{token}) {
         my $request_token = Jifty::Plugin::OAuth::Model::RequestToken->new(current_user => Jifty::CurrentUser->superuser);
-        $request_token->load_by_cols(token => $oauth_params{token}, authorized => '');
+        $request_token->load_by_cols(token => $oauth_params{token}, authorized => 0);
 
         if ($request_token->id) {
             set consumer => $request_token->consumer;
@@ -127,7 +129,7 @@
 =cut
 
 sub authorize_post {
-    abortmsg(403, "Cannot authorize tokens as an OAuthed user") if Jifty->handler->stash->{oauth};
+    abortmsg(403, "Cannot authorize tokens as an OAuthed user") if Jifty->web->current_user->is_oauthed;
     my $result = Jifty->web->response->result("authorize_request_token");
     unless ($result && $result->success) {
         redirect '/oauth/authorize';
@@ -179,18 +181,13 @@
     # make sure the signature matches the rest of what the consumer gave us
     abortmsg(401, "Invalid signature (type: $oauth_params{signature_method}).") unless $request->verify;
 
-    my $token = Jifty::Plugin::OAuth::Model::AccessToken->new(current_user => Jifty::CurrentUser->superuser);
-
-    ($ok, $msg) = eval {
-        $token->create(consumer => $consumer,
-                       auth_as  => $request_token->authorized_by);
-    };
+    my $token = Jifty::Plugin::OAuth::Model::AccessToken->create_from_request_token($request_token);
 
     abortmsg(401, "Unable to create an Access Token: " . $@ || $msg)
         if $@ || !defined($token) || !$ok;
 
     $consumer->made_request(@oauth_params{qw/timestamp nonce/});
-    $request_token->set_used('t');
+    $request_token->set_used(1);
 
     set oauth_response => {
         oauth_token        => $token->token,
@@ -216,7 +213,7 @@
     my @params = qw/consumer_key signature_method signature
                     timestamp nonce token version/;
     set no_abort => 1;
-    my %oauth_params  = get_parameters(@params);
+    my %oauth_params = get_parameters(@params);
     for (@params) {
         abortmsg(undef, "Undefined required parameter: $_"), return if !defined($oauth_params{$_});
     }
@@ -258,8 +255,27 @@
     abortmsg(undef, "Invalid signature (type: $oauth_params{signature_method})."), return unless $request->verify;
 
     $consumer->made_request(@oauth_params{qw/timestamp nonce/});
-    Jifty->handler->stash->{oauth} = 1;
-    Jifty->web->temporary_current_user(Jifty->app_class('CurrentUser')->new(id => $access_token->auth_as));
+
+    my $new_current_user = Jifty->app_class('CurrentUser')->new(
+        id => $access_token->auth_as,
+    );
+    $new_current_user->is_oauthed(1);
+    $new_current_user->oauth_token($access_token);
+
+    Jifty->web->temporary_current_user($new_current_user);
+
+    Jifty->log->info("Consumer " . $consumer->name . " successfully OAuthed as user ". $access_token->auth_as);
+}
+
+=head2 invalid_method
+
+This aborts the request with an "invalid HTTP method" response code.
+
+=cut
+
+sub invalid_method {
+    Jifty->web->response->add_header(Allow => 'POST');
+    abort(405);
 }
 
 =head2 get_consumer CONSUMER KEY
@@ -295,7 +311,15 @@
 
 {
     my %valid_signature_methods = map { $_ => 1 }
-                                  qw/PLAINTEXT HMAC-SHA1 RSA-SHA1/;
+                                  qw/PLAINTEXT HMAC-SHA1 /;
+
+    if (eval { require Crypt::OpenSSL::RSA; 1 }) {
+        $valid_signature_methods{"RSA-SHA1"} = 1;
+    }
+    else {
+        Jifty->log->debug("RSA-SHA1 support for OAuth unavailable: Crypt::OpenSSL::RSA is not installed.");
+    }
+
     my %key_field = ('RSA-SHA1' => 'rsa_key');
 
     sub get_signature_key {

Modified: jifty/branches/jquery/lib/Jifty/Plugin/OAuth/Model/AccessToken.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Plugin/OAuth/Model/AccessToken.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Plugin/OAuth/Model/AccessToken.pm	Sun Mar 16 17:45:40 2008
@@ -5,6 +5,8 @@
 
 use base qw( Jifty::Plugin::OAuth::Token Jifty::Record );
 
+use constant is_private => 1;
+
 # kludge 1: you cannot call Jifty->app_class within schema {}
 # kludge 3: due to the loading order, you can't really do this
 #my $app_user;
@@ -33,6 +35,8 @@
     column consumer =>
         refers_to Jifty::Plugin::OAuth::Model::Consumer;
 
+    column can_write =>
+        is boolean;
 };
 
 =head2 table
@@ -43,6 +47,34 @@
 
 sub table {'oauth_access_tokens'}
 
+=head2 create_from_request_token
+
+This creates a new access token (as the superuser) and populates its values
+from the given request token.
+
+=cut
+
+sub create_from_request_token {
+    my $self = shift;
+    my $request_token = shift;
+
+    if (!ref($self)) {
+        $self = $self->new(current_user => Jifty::CurrentUser->superuser);
+    }
+
+    my $restrictions = $request_token->access_token_restrictions
+        or die "No access-token restrictions given in the request token.";
+
+    $self->create(
+        consumer    => $request_token->consumer,
+        auth_as     => $request_token->authorized_by,
+        valid_until => $restrictions->{use_limit},
+        can_write   => $restrictions->{can_write} ? 1 : 0,
+    );
+
+    return $self;
+}
+
 =head2 is_valid
 
 This neatly encapsulates the "is this access token perfect?" check.
@@ -65,5 +97,21 @@
     return (1, "Request token valid");
 }
 
+=head2 current_user_can
+
+Only root may have access to this model.
+
+In the near future, we should allow the authorizing user to edit this token
+(taking care of course that the authorizing user is not actually authed via
+OAuth!)
+
+=cut
+
+sub current_user_can {
+    my $self = shift;
+
+    return $self->current_user->is_superuser;
+}
+
 1;
 

Modified: jifty/branches/jquery/lib/Jifty/Plugin/OAuth/Model/Consumer.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Plugin/OAuth/Model/Consumer.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Plugin/OAuth/Model/Consumer.pm	Sun Mar 16 17:45:40 2008
@@ -5,6 +5,8 @@
 
 use base qw( Jifty::Record );
 
+use constant is_private => 1;
+
 use Jifty::DBI::Schema;
 use Jifty::Record schema {
 
@@ -68,7 +70,7 @@
 
 sub before_set_last_timestamp {
     my $self = shift;
-    my $new_ts = shift;
+    my $new_ts = shift->{value};
 
     # uh oh, looks like sloppy coding..
     if ($new_ts < $self->last_timestamp) {
@@ -94,6 +96,7 @@
 
 sub is_valid_request {
     my ($self, $timestamp, $nonce) = @_;
+
     return (0, "Timestamp nonincreasing, $timestamp < ".$self->last_timestamp.".")
         if $timestamp < $self->last_timestamp;
     return 1 if $timestamp > $self->last_timestamp;
@@ -121,5 +124,17 @@
     $self->set_nonces({ %{$self->nonces}, $nonce => 1 });
 }
 
+=head2 current_user_can
+
+Only root may have access to this model.
+
+=cut
+
+sub current_user_can {
+    my $self = shift;
+
+    return $self->current_user->is_superuser;
+}
+
 1;
 

Modified: jifty/branches/jquery/lib/Jifty/Plugin/OAuth/Model/RequestToken.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Plugin/OAuth/Model/RequestToken.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Plugin/OAuth/Model/RequestToken.pm	Sun Mar 16 17:45:40 2008
@@ -5,6 +5,8 @@
 
 use base qw( Jifty::Plugin::OAuth::Token Jifty::Record );
 
+use constant is_private => 1;
+
 # kludge 1: you cannot call Jifty->app_class within schema {}
 # kludge 3: due to the loading order, you can't really do this
 #my $app_user;
@@ -19,8 +21,7 @@
         is required;
 
     column authorized =>
-        type is 'boolean',
-        default is '';
+        is boolean;
 
     # kludge 2: this kind of plugin cannot yet casually refer_to app models
     column authorized_by =>
@@ -31,11 +32,8 @@
         refers_to Jifty::Plugin::OAuth::Model::Consumer,
         is required;
 
-    # kludge 3: Jifty::DBI + SQLite = poor boolean handling
-    # so the empty string is the false value, 't' is the true value
     column used =>
-        type is 'boolean',
-        default is '';
+        is boolean;
 
     column token =>
         type is 'varchar',
@@ -45,6 +43,9 @@
         type is 'varchar',
         is required;
 
+    column access_token_restrictions =>
+        type is 'blob',
+        filters are 'Jifty::DBI::Filter::Storable';
 };
 
 =head2 table
@@ -65,7 +66,6 @@
 sub after_set_authorized {
     my $self = shift;
     $self->set_authorized_by(Jifty->web->current_user->id);
-    $self->set_valid_until(DateTime->now->add(hours => 1));
 }
 
 =head2 can_trade_for_access_token
@@ -96,5 +96,17 @@
     return (1, "Request token valid");
 }
 
+=head2 current_user_can
+
+Only root may have access to this model.
+
+=cut
+
+sub current_user_can {
+    my $self = shift;
+
+    return $self->current_user->is_superuser;
+}
+
 1;
 

Modified: jifty/branches/jquery/lib/Jifty/Plugin/OAuth/View.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Plugin/OAuth/View.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Plugin/OAuth/View.pm	Sun Mar 16 17:45:40 2008
@@ -37,11 +37,37 @@
 =cut
 
 template 'oauth' => page {
+    title    => 'OAuth',
+    subtitle => 'Information',
+}
+content {
     p {
-        b { a { attr { href => "http://oauth.net/" } "OAuth" } };
-        outs " is an open protocol to allow secure authentication to users' private data."
+        b {
+            hyperlink(
+                url    => "http://oauth.net/",
+                label  => "OAuth",
+                target => "_blank",
+            )
+        };
+        outs " is an open protocol to allow secure authentication to users' private data. It's far more secure than users giving out their passwords."
     }
 
+    h2 { "Users" }
+
+    p {
+        "OAuth is nearly transparent to end users. Through OAuth, other applications can have secure -- and time-limited -- read and write access to your data on this site."
+    }
+    p {
+        outs "Applications may ask you to ";
+        hyperlink(
+            label => "authorize a 'token' on our site",
+            url   => Jifty->web->url(path => '/oauth/authorize'),
+        );
+        outs ". This is normal. We want to make sure you approve of other people looking at your data.";
+    }
+
+    h2 { "Consumers" }
+
     p {
         "This application supports OAuth. If you'd like to access the private resources of users of this site, you must first establish a Consumer Key, Consumer Secret, and, if applicable, RSA public key with us. You can do so by contacting " . (Jifty->config->framework('AdminEmail')||'us') . ".";
     }
@@ -51,18 +77,14 @@
     }
 
     dl {
-        dt {
-            outs "Request a Request Token";
-            dd { Jifty->web->url(path => '/oauth/request_token') }
-        }
-        dt {
-            outs "Obtain user authorization for a Request Token";
-            dd { Jifty->web->url(path => '/oauth/authorize') }
-        }
-        dt {
-            outs "Exchange a Request Token for an Access Token";
-            dd { Jifty->web->url(path => '/oauth/access_token') }
-        }
+        dt { "Request a Request Token" }
+        dd { Jifty->web->url(path => '/oauth/request_token') }
+
+        dt { "Obtain user authorization for a Request Token" }
+        dd { Jifty->web->url(path => '/oauth/authorize') }
+
+        dt { "Exchange a Request Token for an Access Token" }
+        dd { Jifty->web->url(path => '/oauth/access_token') }
     }
 
     p {
@@ -77,11 +99,13 @@
         outs "While you have a valid access token, you may browse the site as the user normally does.";
 
         if ($restful) {
-            outs " You may also use our REST interface. See ";
-            a {
-                attr { href => Jifty->web->url(path => '=/help') }
-                Jifty->web->url(path => '=/help')
-            }
+            outs " You may also use ";
+            hyperlink(
+                url    => Jifty->web->url(path => '=/help'),
+                label  => "our REST interface",
+                target => "_blank",
+            );
+            outs ".";
         }
     }
 };
@@ -94,7 +118,10 @@
 
 =cut
 
-template 'oauth/authorize' => page { title => 'Someone wants stuff!' }
+template 'oauth/authorize' => page {
+    title => 'OAuth',
+    subtitle => 'Someone wants stuff!',
+}
 content {
     show '/oauth/help';
 
@@ -116,16 +143,19 @@
         $authorize->form_field('token')->render;
     }
 
+    $authorize->form_field('use_limit')->render;
+    $authorize->form_field('can_write')->render;
+
     outs_raw $authorize->hidden(callback => get 'callback');
 
     outs_raw($authorize->button(
-        label => 'Allow',
-        arguments => { %args, authorize => 'allow' },
+        label => 'Deny',
+        arguments => { %args, authorize => 'deny' },
     ));
 
     outs_raw($authorize->button(
-        label => 'Deny',
-        arguments => { %args, authorize => 'deny' },
+        label => 'Allow',
+        arguments => { %args, authorize => 'allow' },
     ));
 
     Jifty->web->form->end();
@@ -138,7 +168,10 @@
 
 =cut
 
-template 'oauth/authorized' => page { title => 'XXX' }
+template 'oauth/authorized' => page {
+    title    => 'OAuth',
+    subtitle => 'Authorized',
+}
 content {
     my $result    = get 'result';
     my $callback  = $result->content('callback');
@@ -179,10 +212,17 @@
     div {
         p {
             show '/oauth/consumer';
-            outs ' is trying to access some of your data on this site. If you trust this application, you may grant it access. Note that access is read-only and will expire in one hour.';
+            outs ' is trying to access your data on this site. If you trust this application, you may grant it access.';
         }
         p {
-            "If you're at all uncomfortable with the idea of someone rifling through your things, click Deny."
+            "If you're at all uncomfortable with the idea of someone rifling through your things, or don't know what this is, click Deny."
+        }
+        p {
+            hyperlink(
+                label  => "Learn more about OAuth.",
+                url    => "http://oauth.net/",
+                target => "_blank",
+            )
         }
     }
 };
@@ -200,7 +240,11 @@
         outs ref($consumer) ? $consumer->name : $consumer;
         if (ref($consumer) && $consumer->url) {
             outs ' <';
-            a { attr { href => $consumer->url } $consumer->url };
+            hyperlink(
+                url    => $consumer->url,
+                label  => $consumer->url,
+                target => "_blank",
+            );
             outs ' >';
         }
     }

Modified: jifty/branches/jquery/lib/Jifty/Plugin/REST.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Plugin/REST.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Plugin/REST.pm	Sun Mar 16 17:45:40 2008
@@ -4,7 +4,7 @@
 package Jifty::Plugin::REST;
 use base qw/Jifty::Plugin/;
 
-our $VERSION = '1.00';
+our $VERSION = '1.01';
 
 =head1 NAME
 

Modified: jifty/branches/jquery/lib/Jifty/Plugin/Recorder.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Plugin/Recorder.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Plugin/Recorder.pm	Sun Mar 16 17:45:40 2008
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 use base qw/Jifty::Plugin Class::Data::Inheritable/;
-__PACKAGE__->mk_accessors(qw/start path loghandle logged_request memory_usage/);
+__PACKAGE__->mk_accessors(qw/start path loghandle request_time logged_request memory_usage/);
 
 use Time::HiRes 'time';
 use Jifty::Util;
@@ -59,10 +59,18 @@
     my $cgi     = shift;
 
     $self->logged_request(0);
+    $self->request_time(time);
 
     eval {
-        my $delta = time - $self->start;
-        my $request = { cgi => nfreeze($cgi), ENV => \%ENV, time => $delta };
+        my $delta = $self->request_time - $self->start;
+
+        my $request = {
+            cgi   => nfreeze($cgi),
+            ENV   => \%ENV,
+            time  => $delta,
+            start => $self->request_time,
+        };
+
         my $yaml = Jifty::YAML::Dump($request);
 
         print { $self->get_loghandle } $yaml;
@@ -92,6 +100,8 @@
 
     if ($self->logged_request) {
         eval {
+            print { $self->get_loghandle } "end: " . time . "\n";
+            print { $self->get_loghandle } "took: " . (time - $self->request_time) . "\n";
             print { $self->get_loghandle } "current_user: " . (Jifty->web->current_user->id || 0) . "\n";
 
             # get memory usage. yes, we really do need to go through these

Modified: jifty/branches/jquery/lib/Jifty/Request.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Request.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Request.pm	Sun Mar 16 17:45:40 2008
@@ -218,6 +218,7 @@
             path      => $f->{path},
             arguments => $f->{args},
             wrapper   => $f->{wrapper} || 0,
+            in_form   => $f->{in_form},
         );
         while ( ref $f->{parent} eq "HASH" and $f = $f->{parent} ) {
             $current = $current->parent(
@@ -596,14 +597,31 @@
 
 =head2 return_from_continuation
 
+Returns from the current continuation, if there is one.  If the
+request path doesn't match, we call the continuation again, which
+should redirect to the right place.  If we have to do this, we return
+true, which should be taken as a sign to not process the reqest
+further.
+
 =cut
 
 sub return_from_continuation {
     my $self = shift;
     return unless $self->continuation_type and $self->continuation_type eq "return" and $self->continuation;
-    return $self->continuation->call unless $self->continuation->return_path_matches;
+    unless ($self->continuation->return_path_matches) {
+        # This aborts via Jifty::Dispatcher::_abort -- but we're not
+        # in the dispatcher yet, so it would go uncaught.  Catch it
+        # here.
+        eval {
+            $self->continuation->call;
+        };
+        my $err = $@;
+        warn $err if $err and $err ne "ABORT";
+        return 1;
+    }
     $self->log->debug("Returning from continuation ".$self->continuation->id);
-    return $self->continuation->return;
+    $self->continuation->return;
+    return undef;
 }
 
 =head2 path
@@ -837,12 +855,13 @@
                 path      => undef,
                 arguments => undef,
                 wrapper   => undef,
+                in_form   => undef,
                 @_
                );
 
     my $fragment = $self->{'fragments'}->{ $args{'name'} } || Jifty::Request::Fragment->new;
 
-    for my $k (qw/name path wrapper/) {
+    for my $k (qw/name path wrapper in_form/) {
         $fragment->$k($args{$k}) if defined $args{$k};
     } 
     
@@ -975,7 +994,7 @@
 
 package Jifty::Request::Fragment;
 use base 'Class::Accessor::Fast';
-__PACKAGE__->mk_accessors( qw/name path wrapper arguments parent/ );
+__PACKAGE__->mk_accessors( qw/name path wrapper in_form arguments parent/ );
 
 =head2 Jifty::Request::Fragment
 
@@ -987,6 +1006,8 @@
 
 =head3 wrapper [BOOLEAN]
 
+=head3 in_form [BOOLEAN]
+
 =head3 argument NAME [VALUE]
 
 =head3 arguments

Modified: jifty/branches/jquery/lib/Jifty/Script/App.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Script/App.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Script/App.pm	Sun Mar 16 17:45:40 2008
@@ -105,7 +105,7 @@
 sub _make_directories {
     my $self = shift;
 
-    mkdir($self->prefix);
+    mkdir($self->prefix) or die("Can't create " . $self->prefix . ": $!");
     my @dirs = qw( lib );
     my @dir_parts = split('::',$self->mod_name);
     my $lib_dir = "";

Added: jifty/branches/jquery/lib/Jifty/Script/Repl.pm
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/lib/Jifty/Script/Repl.pm	Sun Mar 16 17:45:40 2008
@@ -0,0 +1,50 @@
+use strict;
+use warnings;
+
+package Jifty::Script::Repl;
+use base qw/App::CLI::Command/;
+use Devel::REPL::Script;
+
+=head1 NAME
+
+Jifty::Script::Repl - A REPL for your Jifty application
+
+=head1 DESCRIPTION
+
+This gives you a L<Devel::REPL> for your Jifty application. L<Devel::REPL> is a
+powerful and extensible read-eval-print-loop (basically a line-by-line
+interpreter). This command is similar to, but more useful than,
+C<jifty console>. C<jifty console> is literally read-eval-print-loop but without
+any of the nice plugins (such as colors, auto-dump output, interrupt
+long-running commands, autorefresh code each line, etc).
+
+=head1 METHODS
+
+=head2 options()
+
+Returns nothing. This script has no options now. Maybe it will have
+some command lines options in the future.
+
+=cut
+
+sub options { }
+
+=head2 run()
+
+Creates a L<Devel::REPL> object and runs it.
+
+=cut
+
+sub run {
+    my $self = shift;
+    Jifty->new();
+    Devel::REPL::Script->new->run();
+}
+
+1;
+
+=head1 AUTHOR
+
+Shawn M Moore C<<sartak at bestpractical.com>>
+
+=cut

Modified: jifty/branches/jquery/lib/Jifty/Test.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Test.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Test.pm	Sun Mar 16 17:45:40 2008
@@ -421,6 +421,9 @@
         $^O eq 'MSWin32') {
         require Jifty::TestServer;
         unshift @Jifty::Server::ISA, 'Jifty::TestServer';
+    } elsif ($ENV{JIFTY_APACHETEST}) {
+        require Jifty::TestServer::Apache;
+        unshift @Jifty::Server::ISA, 'Jifty::TestServer::Apache';
     }
     else {
         require Test::HTTP::Server::Simple;
@@ -430,7 +433,7 @@
     my $server = Jifty::Server->new;
 
     return $server;
-} 
+}
 
 
 =head2 web

Added: jifty/branches/jquery/lib/Jifty/TestServer/Apache.pm
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/lib/Jifty/TestServer/Apache.pm	Sun Mar 16 17:45:40 2008
@@ -0,0 +1,97 @@
+package Jifty::TestServer::Apache;
+
+use strict;
+use warnings;
+use File::Spec;
+use Test::Builder;
+my $Tester = Test::Builder->new;
+
+# explicitly ignore ClassLoader objects in @INC,
+# which'd be ignored in the end, though.
+my $INC = [grep { defined } map { File::Spec->rel2abs($_) } grep { !ref } @INC ];
+
+=head1 NAME
+
+Jifty::TestServer::Apache - Starting and stopping an apache server for tests
+
+=head1 DESCRIPTION
+
+=head1 METHOD
+
+=head2 started_ok
+
+Like started_ok in C<Test::HTTP::Server::Simple>, start the server and
+return the URL.
+
+=cut
+
+sub started_ok {
+    my $self = shift;
+    my $text = shift;
+    $text = 'started server' unless defined $text;
+
+    $self->{pidfile} = File::Temp->new;
+    close $self->{pidfile};
+    $self->{pidfile} .= "";
+    my $ipc = File::Temp::tempdir( CLEANUP => 1 );
+    my $errorlog = File::Temp->new;
+    my $config = File::Temp->new;
+
+    my $PATH = Jifty::Util->absolute_path("bin/jifty");
+    my $STATIC = Jifty::Util->absolute_path(Jifty->config->framework('Web')->{StaticRoot});
+
+    print $config <<"CONFIG";
+ServerName 127.0.0.1
+Port @{[$self->port]}
+User @{[scalar getpwuid($<)]}
+Group @{[scalar getgrgid($()]}
+MinSpareServers 1
+StartServers 1
+PidFile @{[$self->{pidfile}]}
+ErrorLog $errorlog
+<Location />
+    Options FollowSymLinks ExecCGI
+</Location>
+FastCgiIpcDir $ipc
+FastCgiServer $PATH -initial-env JIFTY_COMMAND=fastcgi  -idle-timeout 300  -processes 1 -initial-env PERL5LIB=@{[join(":",@{$INC})]}
+ScriptAlias  / $PATH/
+Alias /static/ $STATIC/
+CONFIG
+    close $config;
+
+    if (fork()) {
+        my $pid;
+        for (1..15) {
+            last if $pid = $self->pids;
+            sleep 1;
+        }
+        if ($pid) {
+            $self->{started} = 1;
+            $Tester->ok(1, $text);
+            return "http://localhost:".$self->port;
+        } else {
+            $Tester->ok(0, $text);
+            return "";
+        }
+    }
+
+    exec($ENV{JIFTY_APACHETEST}, "-f", $config);
+}
+
+sub pids {
+    my $self = shift;
+    return unless -e $self->{pidfile};
+    my $pid = do {local @ARGV = ($self->{pidfile}); scalar <>};
+    chomp $pid;
+    return ($pid);
+}
+
+sub DESTROY {
+    return unless $_[0]->{started};
+    my($pid) = $_[0]->pids;
+    kill(15, $pid) if $pid;
+    1 while ($_ = wait()) >= 0;
+    sleep 1 while kill(0, $pid);
+}
+
+1;

Modified: jifty/branches/jquery/lib/Jifty/Util.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Util.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Util.pm	Sun Mar 16 17:45:40 2008
@@ -345,7 +345,17 @@
 sub reference_to_data {
     my ($self, $obj) = @_;
     (my $model = ref($obj)) =~ s/::/./g;
-    return { jifty_model_reference => 1, id => $obj->id, model => $model };
+    my $id = $obj->id;
+
+    # probably a file extension, from the REST rewrite
+    my $extension = $ENV{HTTP_ACCEPT} =~ /^\w+$/ ? ".$ENV{HTTP_ACCEPT}" : '';
+
+    return {
+        jifty_model_reference => 1,
+        id                    => $obj->id,
+        model                 => $model,
+        url                   => Jifty->web->url(path => "/=/model/$model/id/$id$extension"),
+    };
 }
 
 =head2 stringify LIST

Modified: jifty/branches/jquery/lib/Jifty/View/Declare/CRUD.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/View/Declare/CRUD.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/View/Declare/CRUD.pm	Sun Mar 16 17:45:40 2008
@@ -462,7 +462,7 @@
 template 'list' => sub {
     my $self = shift;
 
-    my ( $page ) = get(qw(page ));
+    my ( $page ) = get('page');
     my $item_path = get('item_path') || $self->fragment_for("view");
     my $collection =  $self->_current_collection();
     div { {class is 'crud-'.$self->object_type}; 
@@ -489,9 +489,9 @@
 # unlimited collection if there is no current search.
 sub _current_collection {
     my $self = shift; 
-    my ( $page, $search_collection ) = get(qw(page  search_collection));
+    my ( $page ) = get('page');
     my $collection_class = $self->record_class->collection_class;
-    my $search = $search_collection || ( Jifty->web->response->result('search') ? Jifty->web->response->result('search')->content('search') : undef );
+    my $search = ( Jifty->web->response->result('search') ? Jifty->web->response->result('search')->content('search') : undef );
     my $collection;
     if ( $search ) {
         $collection = $search;
@@ -700,7 +700,7 @@
 
 template 'new_item' => sub {
     my $self = shift;
-    my ( $object_type, $id ) = ( $self->object_type, get('id') );
+    my ( $object_type ) = ( $self->object_type );
 
     my $record_class = $self->record_class;
     my $create = $record_class->as_create_action;

Modified: jifty/branches/jquery/lib/Jifty/View/Declare/Helpers.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/View/Declare/Helpers.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/View/Declare/Helpers.pm	Sun Mar 16 17:45:40 2008
@@ -228,16 +228,28 @@
 If called in scalar context, pulls the first item in C<args> and returns it.
 If called in list context, returns the values of all items in C<args>.
 
-
-
 =cut
 
 sub get {
     if (wantarray) {
-        map { request->template_argument($_) || request->argument($_) } @_;
+        map { _get_single($_) } @_;
     } else {
-        request->template_argument($_[0]) || request->argument( $_[0] );
+        _get_single($_[0]);
+    }
+}
+
+sub _get_single {
+    my $v = request->template_argument($_[0]) || request->argument( $_[0] );
+    return $v if defined $v;
+
+    if (request->top_request ne request() and $v = request->top_request->template_argument($_[0])) {
+        if (ref $v) {
+            warn("The template argument '$_[0]' was not explicitly passed to the current region ('@{[request->path]}'), and thus will not work if the region is ever refreshed.  Unfortunately, it is a reference, so it can't be passed explicitly either.  You'll need to explicitly pass some stringification of what it is to the region.".Carp::longmess);
+        } else {
+            warn("The template argument '$_[0]' was not explicitly passed to the the current region ('@{[request->path]}'), and thus will not work if the region is ever refreshed.  Try passing it explicitly?");
+        }
     }
+    return undef;
 }
 
 
@@ -245,7 +257,6 @@
 
 Sets arguments for later grabbing with L<get>.
 
-
 =cut
 
 

Modified: jifty/branches/jquery/lib/Jifty/View/Static/Handler.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/View/Static/Handler.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/View/Static/Handler.pm	Sun Mar 16 17:45:40 2008
@@ -5,7 +5,6 @@
 use Compress::Zlib ();
 use HTTP::Date ();
 
-
 package Jifty::View::Static::Handler;
 
 use base qw/Jifty::Object/;
@@ -16,7 +15,7 @@
 
 Jifty::View::Static::Handler - Jifty view handler for static files
 
-head1 DESCRIPTION
+=head1 DESCRIPTION
 
 This class takes care of serving out static files for a Jifty application. 
 
@@ -39,15 +38,12 @@
      * if the browser doesn't accept gzipped content
         send the content uncompressed
 
-
-=cut
-
-
 =head2 new
 
 Create a new static file handler. Likely, only the C<Jifty::Handler> needs to do this.
 
 =cut
+
 sub new {
     my $class = shift;
     
@@ -221,12 +217,11 @@
 
 
 sub send_file {
-    my $self       = shift;
-    my $local_path = shift;
-    my $mime_type  = shift;
+    my $self        = shift;
+    my $local_path  = shift;
+    my $mime_type   = shift;
     my $compression = shift;
 
-
     my $fh = IO::File->new( $local_path, 'r' );
     if ( defined $fh ) {
         binmode $fh;
@@ -238,36 +233,50 @@
         Jifty->web->mason->clear_buffer if Jifty->web->mason;
 
         my @file_info = stat($local_path);
-        my $apache = Jifty->handler->apache;
-
-        $apache->header_out( Status => 200 );
+        my $apache    = Jifty->handler->apache;
         $apache->content_type($mime_type);
-        my $now = time();
-     
-        $apache->header_out('Cache-Control' =>  'max-age=259200, public');
-
-        $apache->header_out(Expires =>  HTTP::Date::time2str($now + 31536000));  # Expire in a year
-        $apache->header_out('Last-Modified' =>  HTTP::Date::time2str( $file_info[9]));
-        $apache->header_out('Content-Length' => $file_info[7]) unless ($compression eq 'gzip');  
-
-        $apache->header_out( "Content-Encoding" => "gzip") if ($compression eq 'gzip');
-        $apache->send_http_header();
+        $self->send_http_header( $compression, $file_info[7], $file_info[9] );
 
-        if ($compression eq 'gzip') {
+        if ( $compression eq 'gzip' ) {
             local $/;
             binmode STDOUT;
+
             # XXX TODO: Cache this
             print STDOUT Compress::Zlib::memGzip(<$fh>);
-        } else{
+        }
+        else {
             $apache->send_fd($fh);
         }
         close($fh);
         return 1;
-    } else {
+    }
+    else {
         return undef;
     }
 }
 
+sub send_http_header {
+    my $self = shift;
+    my ($compression, $length, $modified) = @_;
+    my $now    = time();
+    my $apache = Jifty->handler->apache;
+    $apache->header_out( Status          => 200 );
+
+    # Expire in a year
+    $apache->header_out( 'Cache-Control' => 'max-age=31536000, public' );
+    $apache->header_out( 'Expires' => HTTP::Date::time2str( $now + 31536000 ) );
+ 
+    $apache->header_out(
+      'Last-Modified' => HTTP::Date::time2str( $modified ) ) if $modified;
+
+    $apache->header_out( 'Content-Length' => $length )
+      unless ( $compression eq 'gzip' );
+    $apache->header_out( 'Content-Encoding' => "gzip" )
+      if ( $compression eq 'gzip' );
+
+    $apache->send_http_header();
+}
+
 
 =head2 send_not_modified
 

Modified: jifty/branches/jquery/lib/Jifty/Web.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Web.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Web.pm	Sun Mar 16 17:45:40 2008
@@ -68,6 +68,7 @@
     app.js
     app_behaviour.js
     css_browser_selector.js
+    cssQuery-jquery.js
 )]);
 
 use Class::Trigger;
@@ -1169,6 +1170,25 @@
     Jifty->web->javascript_libs([ @{ Jifty->web->javascript_libs }, @_ ]);
 }
 
+=head3 remove_javascript FILE1, FILE2, ...
+
+Removes the given files from C<< Jifty->web->javascript_libs >>.
+
+This is intended for plugins or applications that provide another version of
+the functionality given in our default JS libraries. For example, the CSSQuery
+plugin will get rid of the cssQuery-jQuery.js back-compat script.
+
+=cut
+
+sub remove_javascript {
+    my $self = shift;
+    my %remove = map { $_ => 1 } @_;
+
+    Jifty->web->javascript_libs([
+        grep { !$remove{$_} } @{ Jifty->web->javascript_libs }
+    ]);
+}
+
 =head3 add_external_javascript URL1, URL2, ...
 
 Pushes urls onto C<Jifty->web->external_javascript_libs>

Modified: jifty/branches/jquery/lib/Jifty/Web/Form.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Web/Form.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Web/Form.pm	Sun Mar 16 17:45:40 2008
@@ -194,7 +194,7 @@
         if ( $self->can($_) ) {
             $self->$_($args{$_});
         } else {
-			my (undef, $template, $line) = caller;
+            my (undef, $template, $line) = caller;
             $self->log->warn("Unknown parameter to Jifty->web->form->start: $_ in $template line $line");
         }
     }

Modified: jifty/branches/jquery/lib/Jifty/Web/Form/Field.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Web/Form/Field.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Web/Form/Field.pm	Sun Mar 16 17:45:40 2008
@@ -70,9 +70,7 @@
         render_mode   => 'update' });
     my $args = ref($_[0]) ? $_[0] : {@_};
 
-    my $subclass = ucfirst($args->{render_as} || $args->{type} || 'text');
-    $subclass = 'Jifty::Web::Form::Field::' . $subclass unless $subclass =~ /::/;
-    bless $self, $subclass if Jifty::Util->require($subclass);
+    $self->rebless( $args->{render_as} || $args->{type} || 'text' );
 
     for my $field ( $self->accessors() ) {
         $self->$field( $args->{$field} ) if exists $args->{$field};
@@ -83,8 +81,7 @@
     # but ignore that if the field is a container in the model
     my ($key, $value) = Jifty::Request::Mapper->query_parameters($self->input_name, $self->current_value);
     if ($key ne $self->input_name && !$self->action->arguments->{$self->name}{container}) {
-        Jifty::Util->require('Jifty::Web::Form::Field::Hidden');
-        bless $self, "Jifty::Web::Form::Field::Hidden";
+        $self->rebless('Hidden');
         $self->input_name($key);
         $self->default_value($value);
         $self->sticky_value(undef);
@@ -98,6 +95,21 @@
     return $self;
 }
 
+=head2 $self->rebless($widget)
+
+Turn the current blessed class into the given widget class.
+
+=cut
+
+sub rebless {
+    my ($self, $widget) = @_;
+    my $widget_class = $widget =~ m/::/ ? $widget : "Jifty::Web::Form::Field::".ucfirst($widget);
+
+    $self->log->error("Invalid widget class $widget_class")
+        unless Jifty::Util->require($widget_class);
+
+    bless $self, $widget_class;
+}
 
 =head2 accessors
 

Modified: jifty/branches/jquery/lib/Jifty/Web/PageRegion.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Web/PageRegion.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Web/PageRegion.pm	Sun Mar 16 17:45:40 2008
@@ -286,6 +286,7 @@
             . Jifty::JSON::objToJson( \%arguments, { singlequote => 1 } ) . qq|,| 
             . qq|'| . $self->path . qq|',|
             . ( $self->parent ? qq|'| . $self->parent->qualified_name . qq|'| : q|null|)
+            . qq|,| . (Jifty->web->form->is_open ? '1' : 'null')
             . qq|);\n|
             . qq|</script>|;
         if ($self->lazy) {

Modified: jifty/branches/jquery/share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list/list
==============================================================================
--- jifty/branches/jquery/share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list/list	(original)
+++ jifty/branches/jquery/share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list/list	Sun Mar 16 17:45:40 2008
@@ -46,7 +46,6 @@
    $collection->order_by(column => $sort_by, order=>'DESC') if ($sort_by && $order);
 } else {
     $collection = $search->content('search');
-    warn $collection->build_select_query;
 }
 
 $collection->set_page_info( current_page => $page,
@@ -84,7 +83,12 @@
 % if ($collection->pager->total_entries == 0) {
  <% _('No items found') %>
 % } else {
+% if ($collection->pager->total_entries == 1) { # totally the wrong way to do it, but this was irritating me
+  <% _('1 entry') %> 
+% } else {
   <% _('%1 entries', $collection-> count) %> 
+% }
+
   <& $list_path.'header', object_type => $object_type, list_path => $list_path, 
     mask_field => $limit_field, mask_val => $limit_val, sort_by => $sort_by, order => $order &>
 % }

Modified: jifty/branches/jquery/share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list/new_item
==============================================================================
--- jifty/branches/jquery/share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list/new_item	(original)
+++ jifty/branches/jquery/share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list/new_item	Sun Mar 16 17:45:40 2008
@@ -6,9 +6,14 @@
 $list_path
 </%args>
 <%init>
-my $record_class = Jifty->app_class("Model", $object_type);
-my $create = Jifty->web->new_action(class => 'Create'.$object_type);
+my $create = Jifty->web->new_action(
+    class => "Create$object_type",
+    moniker => "create-$object_type",
+);
 </%init>
+<% Jifty->web->form->start %>
+% Jifty->web->form->register_action($create);
+
 % if ($mask_field) {
   <% $create->hidden($mask_field,$mask_val) %>
 % }
@@ -31,13 +36,15 @@
                    append => $list_path.'view',
                    args   => { 
                               object_type => $object_type,
-                              list_path => $list_path,
+                              list_path   => $list_path,
                               id          => { result_of => $create, name => 'id' },
                              },
                  },
                 ]
     ) %>
 
+<% Jifty->web->form->end %>
+
 <%doc>
 
 When you hit "save" and create a item, you want to put a fragment

Modified: jifty/branches/jquery/share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list/search
==============================================================================
--- jifty/branches/jquery/share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list/search	(original)
+++ jifty/branches/jquery/share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list/search	Sun Mar 16 17:45:40 2008
@@ -9,6 +9,7 @@
 );
 
 </%init>
+<% Jifty->web->form->start %>
 <div class="jifty_admin">
 % for my $arg ($search->argument_names) {
  <% $search->form_field($arg) %>
@@ -23,5 +24,6 @@
     }
   )
 %>
+<% Jifty->web->form->end %>
 <hr />
 </div>

Modified: jifty/branches/jquery/share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list/update
==============================================================================
--- jifty/branches/jquery/share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list/update	(original)
+++ jifty/branches/jquery/share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list/update	Sun Mar 16 17:45:40 2008
@@ -16,6 +16,7 @@
 );
 </%init>
 <div class="jifty_admin update item inline <%$object_type%>">
+<% Jifty->web->form->start %>
 <div class="editlink">
   <% Jifty->web->link(
       label   => _('Save'),
@@ -48,5 +49,6 @@
 %}
 
 <hr />
+<% Jifty->web->form->end %>
 </div>
 

Modified: jifty/branches/jquery/share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list/view
==============================================================================
--- jifty/branches/jquery/share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list/view	(original)
+++ jifty/branches/jquery/share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list/view	Sun Mar 16 17:45:40 2008
@@ -23,6 +23,7 @@
 </%init>
 <div class="jifty_admin read item inline">
   
+<% Jifty->web->form->start %>
 <%
     Jifty->web->form->submit(
         class   => "editlink",
@@ -34,6 +35,7 @@
         ]
         )
 %> 
+<% Jifty->web->form->end %>
 
 <%
     Jifty->web->link(

Modified: jifty/branches/jquery/share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/model/dhandler
==============================================================================
--- jifty/branches/jquery/share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/model/dhandler	(original)
+++ jifty/branches/jquery/share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/model/dhandler	Sun Mar 16 17:45:40 2008
@@ -10,12 +10,10 @@
 
 <h1><% _('Manage records:') %> <%$object_type%></h1>
 
-<% Jifty->web->form->start %>
 <% Jifty->web->region(name => "admin-$object_type",
                       path => "/__jifty/admin/fragments/list/list", 
                       defaults => { object_type => $object_type , 
                                     render_submit => 1 }) %>
-<% Jifty->web->form->end %>
 
 <h2> <% _('Done?') %> </h2>
 <% Jifty->web->link( url => "/__jifty/admin/", label => _('Back to the admin console')) %>

Modified: jifty/branches/jquery/share/po/zh_cn.po
==============================================================================
--- jifty/branches/jquery/share/po/zh_cn.po	(original)
+++ jifty/branches/jquery/share/po/zh_cn.po	Sun Mar 16 17:45:40 2008
@@ -32,7 +32,7 @@
 "\n"
 "如果您不想重设密码,请忽略这封信\n"
 "\n"
-"若要重设密码,请点击以下链接:\n"
+"如果您要重设密码,请点击以下链接:\n"
 "\n"
 "%2\n"
 
@@ -50,7 +50,7 @@
 "\n"
 "您(或自称是您的某人)在 %1 申请注册.\n"
 "\n"
-"请点击以下链接,以确认你的电子邮箱:\n"
+"请点击以下链接,以确认您的电子邮箱:\n"
 "\n"
 "%2\n"
 
@@ -218,7 +218,7 @@
 
 #: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:97
 msgid "Confirmation resent."
-msgstr ""
+msgstr "确认信件已重新发送"
 
 #: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:213
 msgid "Create"
@@ -243,7 +243,7 @@
 
 #: lib/Jifty/Action/Record/Delete.pm:76
 msgid "Deleted"
-msgstr "成功删除项目."
+msgstr "已删除"
 
 #:
 msgid "Dismiss"
@@ -292,7 +292,7 @@
 
 #: lib/Jifty/Plugin/ErrorTemplates/View.pm:138 share/web/templates/dhandler:7
 msgid "Go back home..."
-msgstr "回到首页..."
+msgstr "回首页..."
 
 #: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:34
 msgid "Hashed Password"
@@ -300,7 +300,7 @@
 
 #: lib/Jifty/Plugin/ErrorTemplates/View.pm:44
 msgid "Head on back home"
-msgstr ""
+msgstr "回首页"
 
 #: lib/Jifty/I18N.pm:19 lib/Jifty/I18N.pm:23
 #. ('World')
@@ -310,7 +310,7 @@
 #: lib/Jifty/Plugin/SkeletonApp/View.pm:31 share/web/templates/_elements/sidebar:5
 #. ($u->$method()
 msgid "Hiya, %1."
-msgstr "您好,%1。"
+msgstr "您好, %1."
 
 #: lib/Jifty/Plugin/SkeletonApp/Dispatcher.pm:23
 msgid "Home"
@@ -318,7 +318,7 @@
 
 #: lib/Jifty/Plugin/User/Mixin/Model/User.pm:30
 msgid "How should I display your name to other users?"
-msgstr "您在本系统的名字"
+msgstr "您在本系统中的名字?"
 
 #: lib/Jifty/Action.pm:1090
 msgid "I changed $field for you"
@@ -338,7 +338,7 @@
 
 #: lib/Jifty/Plugin/User/Mixin/Model/User.pm:89
 msgid "It looks like somebody else is using that address. Is there a chance you have another account?"
-msgstr "已经有人使用了这个地址,会不会是因为您还有另外一个账号?"
+msgstr "已经有人使用了这个地址,或许您还有另外一个账号?"
 
 #: lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:75
 msgid "It looks like you already have an account. Perhaps you want to <a href=\"/login\">log in</a> instead?"
@@ -350,7 +350,7 @@
 
 #: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:82
 msgid "It looks like you're already confirmed."
-msgstr "您已经确认过了."
+msgstr "您的账号已经通过验证了."
 
 #:
 msgid "Jifty Administrative Console"
@@ -362,7 +362,7 @@
 
 #:
 msgid "Jifty Pod Online"
-msgstr "Jifty 在线 POD 文件"
+msgstr "Jifty 在线 POD 文档"
 
 #: lib/Jifty/View/Declare/Helpers.pm:375 share/web/templates/_elements/wrapper:18
 msgid "Loading..."
@@ -423,7 +423,7 @@
 
 #: lib/Jifty/Plugin/Authentication/Password/View.pm:52
 msgid "No account yet? It's quick and easy. "
-msgstr "还没有账号?您可以方便快捷地注册. "
+msgstr "还没有账号?您可以很容易就注册一个. "
 
 #: lib/Jifty/Action/Record/Search.pm:130
 msgid "No field contains"
@@ -439,11 +439,11 @@
 
 #:
 msgid "Online Documentation"
-msgstr "在线文件"
+msgstr "在线文档"
 
 #: lib/Jifty/Plugin/OnlineDocs/Dispatcher.pm:26
 msgid "Online docs"
-msgstr "在线文件"
+msgstr "在线文档"
 
 #: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:134
 #. ($page, $collection->pager->last_page)
@@ -472,7 +472,7 @@
 
 #: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:66
 msgid "Please email us!"
-msgstr "请发邮件通知我们!"
+msgstr "请给我们发邮件!"
 
 #: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:163
 msgid "Previous Page"
@@ -575,7 +575,7 @@
 
 #: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:71 lib/Jifty/Plugin/Authentication/Password/Action/SendPasswordReminder.pm:72 lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:70
 msgid "That doesn't look like an email address."
-msgstr "这不是合法的电子邮箱."
+msgstr "不是合法的电子邮箱."
 
 #: lib/Jifty/Action/Record.pm:249
 msgid "That doesn't look right, but I don't know why"
@@ -583,7 +583,7 @@
 
 #: lib/Jifty/Action/Record.pm:181
 msgid "The passwords you typed didn't match each other"
-msgstr "两组密码不匹配."
+msgstr "密码不匹配."
 
 #: lib/Jifty/Web.pm:365
 msgid "There was an error completing the request.  Please try again later."
@@ -599,11 +599,11 @@
 
 #: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:419
 msgid "This console lets you manage the records in your Jifty database. Below, you should see a list of all your database tables. Feel free to go through and add, delete or modify records."
-msgstr "您可利用此界面来管理数据库的内容. 请选择表格名称, 进行增删及编辑."
+msgstr "您可利用此界面来管理数据库的内容. 请选择表格名称, 进行增删或者编辑."
 
 #: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:425
 msgid "To disable this administrative console, add \"AdminMode: 0\" under the \"framework:\" settings in the config file (etc/config.yml)."
-msgstr "如果想停用管理界面, 请在配置文件 (etc/config.yml) 的 \"framework:\" 配置项内加上 \"AdminMode: 0\" 即可."
+msgstr "如果想禁用管理界面, 请在配置文件 (etc/config.yml) 的 \"framework:\" 配置项内加上 \"AdminMode: 0\" 即可."
 
 #: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:127
 msgid "Toggle search"
@@ -615,11 +615,11 @@
 
 #: lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:108
 msgid "Try again later. We're really, really sorry."
-msgstr "请稍后再试,抱歉."
+msgstr "请稍后再试,非常抱歉."
 
 #: lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:53
 msgid "Type that again?"
-msgstr "请再输入一次"
+msgstr "再输入一次?"
 
 #: lib/Jifty/Action/Record/Update.pm:156
 msgid "Updated"
@@ -668,7 +668,7 @@
 
 #: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:63
 msgid "You don't exist."
-msgstr "该帐号不存在."
+msgstr "该账号不存在."
 
 #: lib/Jifty/Plugin/ErrorTemplates/View.pm:130
 msgid "You got to a page that we don't think exists."
@@ -680,7 +680,7 @@
 
 #: lib/Jifty/Plugin/Authentication/Password/Action/ConfirmEmail.pm:44
 msgid "You have already confirmed your account."
-msgstr "您已经确认过该账号了."
+msgstr "您的账号已经通过验证了."
 
 #: lib/Jifty/Plugin/Authentication/Password/View.pm:98
 msgid "You lost your password. A link to reset it will be sent to the following email address:"

Modified: jifty/branches/jquery/share/po/zh_tw.po
==============================================================================
--- jifty/branches/jquery/share/po/zh_tw.po	(original)
+++ jifty/branches/jquery/share/po/zh_tw.po	Sun Mar 16 17:45:40 2008
@@ -804,7 +804,7 @@
 
 #: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:146 lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:152
 msgid "You may have mistyped your email or password. Give it another shot."
-msgstr "你可以打錯了電郵或密碼.請再試一次"
+msgstr "你可能打錯了電郵或密碼.請再試一次"
 
 #: lib/Jifty/Plugin/Authentication/Facebook/Action/LinkFacebookUser.pm:40
 msgid "You must be logged in to link your user to your Facebook account."

Modified: jifty/branches/jquery/share/web/static/js/jifty.js
==============================================================================
--- jifty/branches/jquery/share/web/static/js/jifty.js	(original)
+++ jifty/branches/jquery/share/web/static/js/jifty.js	Sun Mar 16 17:45:40 2008
@@ -313,9 +313,10 @@
                             if (field.nodeName == 'update') {
                                 var field_name = field.getAttribute("name");
                                 for (var form_number = 0 ; form_number < document.forms.length; form_number++) {
-                                    if (document.forms[form_number].elements[field_name] == null)
+                                    var form_field = document.forms[form_number].elements[field_name];
+                                    if (form_field  == null || !form_field.is('.ajaxcanonicalization'))
                                         continue;
-                                    document.forms[form_number].elements[field_name].value = field.firstChild.data;
+                                    form_field.value = field.firstChild.data;
                                 }
                             }
                         }
@@ -503,12 +504,14 @@
 };
 
 /* Forms */
+
 Jifty.Form = {};
 
 jQuery.extend(Jifty.Form, {
     getElements: function(element) {
         return jQuery(":input", element).get();
     },
+
     // Return an Array of Actions that are in this form
     getActions: function (element) {
         var elements = [];
@@ -529,12 +532,12 @@
     }
 });
 
-
 var current_actions = {};
 
 Jifty.Form.Element = {};
 
 /* Fields */
+
 jQuery.extend(Jifty.Form.Element, {
     // Get the moniker for this form element
     // Takes an element or an element id
@@ -664,7 +667,7 @@
 
     buttonActions: function(element) {
         element = Jifty.$(element);
-        var actions = Jifty.Form.Element.buttonArguments(element)[ ('J:ACTIONS') ];
+        var actions = Jifty.Form.Element.buttonArguments(element)['J:ACTIONS'];
         if(actions) {
             return actions.split(",");
         } else {
@@ -796,11 +799,12 @@
 };
 
 Region.prototype = {
-    initialize: function(name, args, path, parent) {
+    initialize: function(name, args, path, parent, in_form) {
         this.name = name;
         this.args = jQuery.extend({}, args);
         this.path = path;
         this.parent = parent ? fragments[parent] : null;
+        this.in_form = in_form;
         if (fragments[name]) {
             // If this fragment already existed, we want to wipe out
             // whatever evil lies we might have said earlier; do this
@@ -929,7 +933,7 @@
             }
 
             // Make the region (for now)
-            new Region(name, f['args'], f['path'], f['parent']);
+            new Region(name, f['args'], f['path'], f['parent'], f['parent'] ? f['parent'].in_form : null);
         } else if ((f['path'] != null) && f['toggle'] && (f['path'] == fragments[name].path)) {
             // If they set the 'toggle' flag, and clicking wouldn't change the path
             jQuery(element).empty();
@@ -1215,6 +1219,9 @@
             // Ask for the wrapper if we are making a new region
             fragment_request['wrapper'] = 1;
 
+        if (fragments.get(name).in_form)
+            fragment_request['in_form'] = 1;
+
         // Push it onto the request stack
         request.fragments[name] = fragment_request;
         ++has_request;
@@ -1643,6 +1650,104 @@
  * after jifty.js.
  */
 
-if (window.Form == null)
-    window.Form = Jifty.Form;
+Form = {};
+
+jQuery.extend(Form, {
+    // Return an Array of Actions that are in this form
+    getActions: function (element) {
+        // DEPRECATED: use Jifty.Form.getActions instead
+        return Jifty.Form.getActions(element);
+    },
+    clearPlaceholders: function(element) {
+        // DEPRECATED: use Jifty.Form.clearPlaceholders instead
+        return Jifty.Form.clearPlaceholders(element);
+    },
+
+    Element: {}
+});
+
+jQuery.extend(Form.Element, {
+    // Get the moniker for this form element
+    // Takes an element or an element id
+    getMoniker: function (element) {
+        // DEPRECATED: use Jifty.Form.Element.getMoniker instead
+        return Jifty.Form.Element.getMoniker(element);
+    },
+
+    // Get the Action for this form element
+    // Takes an element or an element id
+    getAction: function (element) {
+        // DEPRECATED: use Jifty.Form.Element.getAction instead
+        return Jifty.Form.Element.getAction(element);
+    },
+
+    // Returns the name of the field
+    getField: function (element) {
+        // DEPRECATED: use Jifty.Form.Element.getField instead
+        return Jifty.Form.Element.getField(element);
+    },
+
+    // The type of Jifty form element
+    getType: function (element) {
+        // DEPRECATED: use Jifty.Form.Element.getType instead
+        return Jifty.Form.Element.getType(element);
+    },
+
+    // Validates the action this form element is part of
+    validate: function (element) {
+        // DEPRECATED: use Jifty.Form.Element.validate instead
+        return Jifty.Form.Element.validate(element);
+    },
+
+    // Temporarily disable validation
+            disableValidation: function(element) {
+                // DEPRECATED: use Jifty.Form.Element.disableValidation instead
+                return Jifty.Form.Element.disableValidation(element);
+        },
+
+            //Reenable validation            
+            enableValidation: function(element) {
+                // DEPRECATED: use Jifty.Form.Element.enableValidation instead
+                return Jifty.Form.Element.enableValidation(element);
+        },
+
+
+    // Look up the form that this element is part of -- this is sometimes
+    // more complicated than you'd think because the form may not exist
+    // anymore, or the element may have been inserted into a new form.
+    // Hence, we may need to walk the DOM.
+    getForm: function (element) {
+        // DEPRECATED: use Jifty.Form.Element.getForm instead
+        return Jifty.Form.Element.getForm(element);
+    },
+
+    buttonArguments: function(element) {
+        // DEPRECATED: use Jifty.Form.Element.buttonArguments instead
+        return Jifty.Form.Element.buttonArguments(element);
+    },
 
+    buttonActions: function(element) {
+        // DEPRECATED: use Jifty.Form.Element.buttonActions instead
+        return Jifty.Form.Element.buttonActions(element);
+    },  
+
+    buttonFormElements: function(element) {
+        // DEPRECATED: use Jifty.Form.Element.buttonFormElements instead
+        return Jifty.Form.Element.buttonFormElements(element);
+    },
+
+    /* Someday Jifty may have the concept of "default"
+       buttons.  For now, this clicks the first button that will
+       submit the action associated with the form element.
+     */
+    clickDefaultButton: function(element) {
+        // DEPRECATED: use Jifty.Form.Element.clickDefaultButton instead
+        return Jifty.Form.Element.clickDefaultButton(element);
+    },
+
+    handleEnter: function(event) {
+        // DEPRECATED: use Jifty.Form.Element.handleEnter instead
+        return Jifty.Form.Element.handleEnter(event);
+    }
+
+});

Modified: jifty/branches/jquery/share/web/templates/__jifty/webservices/xml
==============================================================================
--- jifty/branches/jquery/share/web/templates/__jifty/webservices/xml	(original)
+++ jifty/branches/jquery/share/web/templates/__jifty/webservices/xml	Sun Mar 16 17:45:40 2008
@@ -15,6 +15,8 @@
 FRAGMENT:
 for my $f ( Jifty->web->request->fragments ) {
     # Set up the region stack
+    Jifty->web->form->is_open(1) if $f->in_form;
+
     local Jifty->web->{'region_stack'} = [];
     my @regions;
     do {
@@ -59,6 +61,8 @@
     $writer->endTag();
 
     Jifty->web->current_region->exit while Jifty->web->current_region;
+
+    Jifty->web->form->is_open(0);
 }
 
 }

Modified: jifty/branches/jquery/t/TestApp-JiftyJS/Makefile.PL
==============================================================================
--- jifty/branches/jquery/t/TestApp-JiftyJS/Makefile.PL	(original)
+++ jifty/branches/jquery/t/TestApp-JiftyJS/Makefile.PL	Sun Mar 16 17:45:40 2008
@@ -1,6 +1,6 @@
 use inc::Module::Install;
 
-name        'TestApp::JiftyJS/';
+name        'TestApp-JiftyJS';
 version     '0.01';
 requires    'Jifty' => '0.70824';
 

Modified: jifty/branches/jquery/t/TestApp-Plugin-AppPluginHasModels/Makefile.PL
==============================================================================
--- jifty/branches/jquery/t/TestApp-Plugin-AppPluginHasModels/Makefile.PL	(original)
+++ jifty/branches/jquery/t/TestApp-Plugin-AppPluginHasModels/Makefile.PL	Sun Mar 16 17:45:40 2008
@@ -1,6 +1,6 @@
 use inc::Module::Install;
 
-name        'TestApp::Plugin::AppPluginHasModels';
+name        'TestApp-Plugin-AppPluginHasModels';
 version     '0.01';
 requires    'Jifty' => '0.70129';
 

Modified: jifty/branches/jquery/t/TestApp-Plugin-Attributes/Makefile.PL
==============================================================================
--- jifty/branches/jquery/t/TestApp-Plugin-Attributes/Makefile.PL	(original)
+++ jifty/branches/jquery/t/TestApp-Plugin-Attributes/Makefile.PL	Sun Mar 16 17:45:40 2008
@@ -1,6 +1,6 @@
 use inc::Module::Install;
 
-name        'TestApp::Plugin::Attributes';
+name        'TestApp-Plugin-Attributes';
 version     '0.01';
 requires    'Jifty' => '0.71129';
 

Added: jifty/branches/jquery/t/TestApp-Plugin-Attributes/t/04-delete.t
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/t/TestApp-Plugin-Attributes/t/04-delete.t	Sun Mar 16 17:45:40 2008
@@ -0,0 +1,88 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+use lib 't/lib';
+use Jifty::SubTest;
+use Jifty::Test tests => 8;
+
+my $song = TestApp::Plugin::Attributes::Model::Song->new;
+my ($ok, $msg) = $song->create(
+    name   => 'Backdrifts',
+    artist => 'Radiohead',
+    album  => 'Hail to the Thief',
+);
+ok($ok, $msg);
+my $song_id = $song->id;
+
+my $song2 = TestApp::Plugin::Attributes::Model::Song->new;
+($ok, $msg) = $song2->create(
+    name   => '15 Step',
+    artist => 'Radiohead',
+    album  => 'In Rainbows',
+);
+ok($ok, $msg);
+my $song2_id = $song2->id;
+
+for (qw/httt radiohead/) {
+    $song->add_attribute(name => 'tag', content => $_);
+    $song2->add_attribute(name => 'tag', content => $_);
+}
+
+$song->add_attribute(name => 'tag', content => 2003);
+$song2->add_attribute(name => 'tag', content => 2007);
+
+my %got = map { $_->content => 1 }
+          @{ $song->attributes->named("tag")->items_array_ref };
+
+::is_deeply(\%got,
+            {httt => 1, radiohead => 1, 2003 => 1},
+            "attributes set correctly");
+
+($ok, $msg) = $song->delete;
+ok($ok, $msg);
+
+my $attrs = TestApp::Plugin::Attributes::Model::AttributeCollection->new(
+    current_user => Jifty::CurrentUser->superuser,
+);
+$attrs->limit(
+    column => 'object_type',
+    value => 'TestApp::Plugin::Attributes::Model::Song',
+);
+$attrs->limit(
+    column => 'object_id',
+    value => $song_id,
+);
+
+is($attrs->count, 0, "deleted all the attributes of the song");
+
+$attrs = TestApp::Plugin::Attributes::Model::AttributeCollection->new(
+    current_user => Jifty::CurrentUser->superuser,
+);
+$attrs->limit(
+    column => 'object_type',
+    value => 'TestApp::Plugin::Attributes::Model::Song',
+);
+$attrs->limit(
+    column => 'object_id',
+    value => $song2_id,
+);
+
+is($attrs->count, 3, "delete only affects the one deleted object deleted");
+
+$ok = $song2->delete_all_attributes;
+ok($ok, "reported success in deleting all attributes");
+
+$attrs = TestApp::Plugin::Attributes::Model::AttributeCollection->new(
+    current_user => Jifty::CurrentUser->superuser,
+);
+$attrs->limit(
+    column => 'object_type',
+    value => 'TestApp::Plugin::Attributes::Model::Song',
+);
+$attrs->limit(
+    column => 'object_id',
+    value => $song2_id,
+);
+
+is($attrs->count, 0, "delete_all_attributes successful");
+

Modified: jifty/branches/jquery/t/TestApp-Plugin-Chart/Makefile.PL
==============================================================================
--- jifty/branches/jquery/t/TestApp-Plugin-Chart/Makefile.PL	(original)
+++ jifty/branches/jquery/t/TestApp-Plugin-Chart/Makefile.PL	Sun Mar 16 17:45:40 2008
@@ -1,6 +1,6 @@
 use inc::Module::Install;
 
-name        'TestApp::Plugin::Chart';
+name        'TestApp-Plugin-Chart';
 version     '0.01';
 requires    'Jifty' => '0.70129';
 

Added: jifty/branches/jquery/t/TestApp-Plugin-Comments/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/t/TestApp-Plugin-Comments/Makefile.PL	Sun Mar 16 17:45:40 2008
@@ -0,0 +1,7 @@
+use inc::Module::Install;
+
+name        'TestApp::Plugin::Comments';
+version     '0.01';
+requires    'Jifty' => '0.71129';
+
+WriteAll;

Added: jifty/branches/jquery/t/TestApp-Plugin-Comments/bin/jifty
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/t/TestApp-Plugin-Comments/bin/jifty	Sun Mar 16 17:45:40 2008
@@ -0,0 +1,11 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+use File::Basename qw(dirname); 
+use UNIVERSAL::require;
+
+use Jifty;
+use Jifty::Script;
+
+local $SIG{INT} = sub { warn "Stopped\n"; exit; };
+Jifty::Script->dispatch();

Added: jifty/branches/jquery/t/TestApp-Plugin-Comments/etc/config.yml
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/t/TestApp-Plugin-Comments/etc/config.yml	Sun Mar 16 17:45:40 2008
@@ -0,0 +1,56 @@
+--- 
+framework: 
+  AdminMode: 1
+  ApplicationClass: TestApp::Plugin::Comments
+  ApplicationName: TestApp::Plugin::Comments
+  ApplicationUUID: E6A5E652-A2D2-11DC-B489-8901F3F60BF3
+  ConfigFileVersion: 2
+  Database: 
+    AutoUpgrade: 1
+    CheckSchema: 1
+    Database: testapp::plugin::comments
+    Driver: SQLite
+    Host: localhost
+    Password: ''
+    RecordBaseClass: Jifty::DBI::Record::Cachable
+    RecordUUIDs: active
+    User: ''
+    Version: 0.0.1
+  DevelMode: 1
+  L10N: 
+    PoDir: share/po
+  LogLevel: INFO
+  Mailer: Sendmail
+  MailerArgs: []
+
+  Plugins: 
+    - LetMe: {}
+    - SkeletonApp: {}
+    - REST: {}
+    - Halo: {}
+    - ErrorTemplates: {}
+    - OnlineDocs: {}
+    - CompressedCSSandJS: {}
+    - AdminUI: {}
+
+    - Comment: {}
+
+  PubSub: 
+    Backend: Memcached
+    Enable: ~
+  SkipAccessControl: 0
+  TemplateClass: TestApp::Plugin::Comments::View
+  Web: 
+    BaseURL: http://localhost
+    DataDir: var/mason
+    Globals: []
+
+    MasonConfig: 
+      autoflush: 0
+      default_escape_flags: h
+      error_format: text
+      error_mode: fatal
+    Port: 8888
+    ServeStaticFiles: 1
+    StaticRoot: share/web/static
+    TemplateRoot: share/web/templates

Added: jifty/branches/jquery/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/Dispatcher.pm	Sun Mar 16 17:45:40 2008
@@ -0,0 +1,15 @@
+use strict;
+use warnings;
+
+package TestApp::Plugin::Comments::Dispatcher;
+use Jifty::Dispatcher -base;
+
+on 'view/#' => run {
+    my $blog = TestApp::Plugin::Comments::Model::BlogPost->new;
+    $blog->load($1);
+
+    set blog => $blog;
+    show 'view';
+};
+
+1;

Added: jifty/branches/jquery/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/Model/BlogPost.pm
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/Model/BlogPost.pm	Sun Mar 16 17:45:40 2008
@@ -0,0 +1,21 @@
+use strict;
+use warnings;
+
+package TestApp::Plugin::Comments::Model::BlogPost;
+use Jifty::DBI::Schema;
+
+use constant CLASS_UUID => '21EC717C-A2D3-11DC-BDD3-A201F3F60BF3';
+
+use TestApp::Plugin::Comments::Record schema {
+    column title => type is 'text';
+    column body => type is 'text';
+    column author => type is 'text';
+    column posted => type is 'text';
+};
+
+use Jifty::Plugin::Comment::Mixin::Model::Commented;
+
+# Your model-specific methods go here.
+
+1;
+

Added: jifty/branches/jquery/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/View.pm
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/t/TestApp-Plugin-Comments/lib/TestApp/Plugin/Comments/View.pm	Sun Mar 16 17:45:40 2008
@@ -0,0 +1,39 @@
+use strict;
+use warnings;
+
+package TestApp::Plugin::Comments::View;
+use Jifty::View::Declare -base;
+
+template 'post' => page {
+    { title is 'New Post' }
+
+    form {
+        my $post_blog = new_action class => 'CreateBlogPost';
+        render_action $post_blog;
+        form_submit label => _('Post'), submit => $post_blog;
+    };
+};
+
+template 'view' => page {
+    my $blog = get 'blog';
+
+    { title is $blog->title }
+
+    p { _('By %1', $blog->author) };
+
+    p { $blog->body };
+
+    hr { };
+
+    render_region
+        name      => 'comments',
+        path      => '/comment/list',
+        arguments => {
+            collapsed    => 1,
+            parent_class => Jifty->app_class('Model', 'BlogPost'),
+            parent_id    => $blog->id,
+        },
+        ;
+};
+
+1;

Added: jifty/branches/jquery/t/TestApp-Plugin-Comments/t/00-model-BlogPost.t
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/t/TestApp-Plugin-Comments/t/00-model-BlogPost.t	Sun Mar 16 17:45:40 2008
@@ -0,0 +1,84 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+A basic test harness for the BlogPost model.
+
+=cut
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test;
+
+eval "use HTML::Scrubber; use MIME::Base64::URLSafe; use Regexp::Common; 1";
+if ($@) {
+    plan skip_all => 'A requirement of the Comment plugin is not installed.';
+}
+else {
+    plan tests => 16;
+}
+
+# Make sure we can load the model
+use_ok('TestApp::Plugin::Comments::Model::BlogPost');
+
+# Grab a system user
+my $system_user = TestApp::Plugin::Comments::CurrentUser->superuser;
+ok($system_user, "Found a system user");
+
+# Try testing a create
+my $o = TestApp::Plugin::Comments::Model::BlogPost->new(current_user => $system_user);
+my ($id) = $o->create();
+ok($id, "BlogPost create returned success");
+ok($o->id, "New BlogPost has valid id set");
+is($o->id, $id, "Create returned the right id");
+
+my $c_list = $o->comments;
+is($c_list->count, 0, 'We have zippo comments');
+
+# Add a comment
+my $c = TestApp::Plugin::Comments::Model::Comment->new(current_user => $system_user);
+$c->create(
+    title      => 'Jifty is da bomb',
+    body       => 'And other overused clichés...',
+    created_on => DateTime->now,
+    your_name  => 'Sterling',
+);
+ok($c->id, 'Created a comment');
+
+my $bpc = TestApp::Plugin::Comments::Model::BlogPostComment->new(current_user => $system_user);
+$bpc->create(
+    commented_upon => $o->id,
+    the_comment    => $c->id,
+);
+ok($bpc->id, 'Created a comment link');
+
+$c_list->redo_search;
+is($c_list->count, 1, 'We have a comment!');
+is($c_list->count_all, 1, 'We have a comment somewhere!');
+
+# And another
+$o->create();
+ok($o->id, "BlogPost create returned another value");
+isnt($o->id, $id, "And it is different from the previous one");
+
+# Searches in general
+my $collection =  TestApp::Plugin::Comments::Model::BlogPostCollection->new(current_user => $system_user);
+$collection->unlimit;
+is($collection->count, 2, "Finds two records");
+
+# Searches in specific
+$collection->limit(column => 'id', value => $o->id);
+is($collection->count, 1, "Finds one record with specific id");
+
+# Delete one of them
+$o->delete;
+$collection->redo_search;
+is($collection->count, 0, "Deleted row is gone");
+
+# And the other one is still there
+$collection->unlimit;
+is($collection->count, 1, "Still one left");
+

Modified: jifty/branches/jquery/t/TestApp-Plugin-CompressedCSSandJS/Makefile.PL
==============================================================================
--- jifty/branches/jquery/t/TestApp-Plugin-CompressedCSSandJS/Makefile.PL	(original)
+++ jifty/branches/jquery/t/TestApp-Plugin-CompressedCSSandJS/Makefile.PL	Sun Mar 16 17:45:40 2008
@@ -1,6 +1,6 @@
 use inc::Module::Install;
 
-name        'TestApp::Plugin::JQuery';
+name        'TestApp-Plugin-JQuery';
 version     '0.01';
 requires    'Jifty' => '0.70129';
 

Modified: jifty/branches/jquery/t/TestApp-Plugin-News/Makefile.PL
==============================================================================
--- jifty/branches/jquery/t/TestApp-Plugin-News/Makefile.PL	(original)
+++ jifty/branches/jquery/t/TestApp-Plugin-News/Makefile.PL	Sun Mar 16 17:45:40 2008
@@ -1,6 +1,6 @@
 use inc::Module::Install;
 
-name        'TestApp::Plugin::News';
+name        'TestApp-Plugin-News';
 version     '0.01';
 requires    'Jifty' => '0.70422';
 

Modified: jifty/branches/jquery/t/TestApp-Plugin-OAuth/Makefile.PL
==============================================================================
--- jifty/branches/jquery/t/TestApp-Plugin-OAuth/Makefile.PL	(original)
+++ jifty/branches/jquery/t/TestApp-Plugin-OAuth/Makefile.PL	Sun Mar 16 17:45:40 2008
@@ -1,6 +1,6 @@
 use inc::Module::Install;
 
-name        'TestApp::Plugin::OAuth';
+name        'TestApp-Plugin-OAuth';
 version     '0.01';
 requires    'Jifty' => '0.70824';
 

Modified: jifty/branches/jquery/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Dispatcher.pm
==============================================================================
--- jifty/branches/jquery/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Dispatcher.pm	(original)
+++ jifty/branches/jquery/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Dispatcher.pm	Sun Mar 16 17:45:40 2008
@@ -6,18 +6,15 @@
 my @login_required = qw{
     oauth/authorize
     nuke/?
+    =/?
 };
 
 my $login_required = join '|', map {"^$_"} @login_required;
-$login_required = qr/$login_required/;
+$login_required = qr/($login_required)/;
 
 before '**' => run {
-    if (Jifty->web->current_user->id) {
-        my $top = Jifty->web->navigation;
-        $top->child( _('Pick!')    => url => '/pick' );
-        $top->child( _('Choices')  => url => '/choices' );
-    }
-    elsif ($1 =~ $login_required) {
+    my $path = $1;
+    if (!Jifty->web->current_user->user_object && $path =~ $login_required) {
         tangent '/login';
     }
 };

Added: jifty/branches/jquery/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Model/Favorite.pm
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Model/Favorite.pm	Sun Mar 16 17:45:40 2008
@@ -0,0 +1,47 @@
+use strict;
+use warnings;
+
+package TestApp::Plugin::OAuth::Model::Favorite;
+use Jifty::DBI::Schema;
+
+use TestApp::Plugin::OAuth::Record schema {
+    column 'owner' =>
+        refers_to TestApp::Plugin::OAuth::Model::User;
+    column 'thing' =>
+        type is 'text';
+};
+
+# you only create favorites for yourself
+sub before_create {
+    my $self = shift;
+    my $args = shift;
+
+    $args->{owner} = Jifty->web->current_user->user_object;
+
+    return 1;
+}
+
+sub current_user_can {
+    my $self  = shift;
+    my $right = shift;
+
+    # all can read
+    return 1 if $right eq 'read';
+
+    # logged in users can create
+    return Jifty->web->current_user->user_object if $right eq 'create';
+
+    # only the owner may update his favorites
+    return 0 unless Jifty->web->current_user->id == $self->owner->id;
+
+    # none can delete
+    return 0 if $right eq 'delete';
+
+    # oauthed can update, non-oauthed can't
+    return !Jifty->web->current_user->is_oauthed if $right eq 'update';
+
+    die "Favorite->current_user_can($right) check fell through";
+}
+
+1;
+

Modified: jifty/branches/jquery/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Model/User.pm
==============================================================================
--- jifty/branches/jquery/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Model/User.pm	(original)
+++ jifty/branches/jquery/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Model/User.pm	Sun Mar 16 17:45:40 2008
@@ -8,11 +8,25 @@
     column 'tasty' =>
         type is 'boolean',
         default is 'f';
+    column 'favorites' =>
+        refers_to TestApp::Plugin::OAuth::Model::FavoriteCollection by 'owner';
 
 };
 
 use Jifty::Plugin::User::Mixin::Model::User;
 use Jifty::Plugin::Authentication::Password::Mixin::Model::User;
 
+sub current_user_can {
+    my $self = shift;
+
+    return 1 if $self->current_user->is_superuser;
+    return 1 if $_[0] eq 'create';
+
+    my $id = $self->__value('id');
+    return 1 if $id == $self->current_user->id;
+
+    $self->SUPER::current_user_can(@_);
+}
+
 1;
 

Modified: jifty/branches/jquery/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Test.pm
==============================================================================
--- jifty/branches/jquery/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Test.pm	(original)
+++ jifty/branches/jquery/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Test.pm	Sun Mar 16 17:45:40 2008
@@ -5,14 +5,13 @@
 use base qw/Jifty::Test/;
 
 use MIME::Base64;
-use Crypt::OpenSSL::RSA;
 use Digest::HMAC_SHA1 'hmac_sha1';
 use Jifty::Test::WWW::Mechanize;
 
 our @EXPORT = qw($timestamp $url $umech $cmech $pubkey $seckey $token_obj
                  $server $URL response_is sign get_latest_token allow_ok deny_ok
                  _authorize_request_token get_request_token get_authorized_token
-                 get_access_token);
+                 get_access_token has_rsa rsa_skip);
 
 our $timestamp = 0;
 our $url;
@@ -23,6 +22,7 @@
 our $token_obj;
 our $server;
 our $URL;
+our $can_write;
 
 sub setup {
     my $class = shift;
@@ -82,16 +82,31 @@
         $cmech->default_header("Authorization" => authz(%params));
     }
 
-    if ($method eq 'POST') {
-        $r = $cmech->post($url, $params_in eq 'method' ? [%params] : ());
-    }
-    else {
+    if ($method eq 'GET') {
         my $query = join '&',
                     map { "$_=" . Jifty->web->escape_uri($params{$_}||'') }
                     keys %params;
         my $params = $params_in eq 'method' ? "?$query" : '';
         $r = $cmech->get("$url$params");
     }
+    else {
+        my $req = HTTP::Request->new(
+            uc($method) => $url,
+        );
+
+        if ($params_in eq 'method') {
+            # avoid Encode complaining about undef
+            for (values %params) {
+                defined or $_ = '';
+            }
+
+            my $content = Jifty->web->query_string(%params);
+            $req->header('Content-type' => 'application/x-www-form-urlencoded');
+            $req->content($content);
+        }
+
+        $r = $cmech->request($req);
+    }
 
     $cmech->default_headers->remove_header("Authorization");
 
@@ -156,6 +171,7 @@
     my $signature;
 
     if ($sig_method eq 'RSA-SHA1') {
+        require Crypt::OpenSSL::RSA;
         my $pubkey = Crypt::OpenSSL::RSA->new_private_key($key);
         $signature = encode_base64($pubkey->sign($signature_base_string), "");
     }
@@ -175,6 +191,15 @@
 
 }
 
+sub has_rsa {
+    eval { require Crypt::OpenSSL::RSA; 1 }
+}
+
+sub rsa_skip {
+    my $count = shift || Carp::carp "You must specify a number of tests to skip.";
+    ::skip 'Crypt::OpenSSL::RSA is required for these tests', $count unless has_rsa;
+}
+
 sub slurp {
     no warnings 'once';
     my $file = shift;
@@ -230,7 +255,12 @@
     ::fail($error), return if $error;
 
     my $name = $token_obj->consumer->name;
-    $umech->content_contains("Allowing $name to access your stuff");
+    if ($can_write) {
+        $umech->content_contains("Allowing $name to read and write your data for 1 hour.");
+    }
+    else {
+        $umech->content_contains("Allowing $name to read your data for 1 hour.");
+    }
 }
 
 sub deny_ok {
@@ -240,7 +270,7 @@
     ::fail($error), return if $error;
 
     my $name = $token_obj->consumer->name;
-    $umech->content_contains("Denying $name the right to access your stuff");
+    $umech->content_contains("Denying $name the right to access your data.");
 }
 
 sub _authorize_request_token {
@@ -258,8 +288,10 @@
         or return "Content did not much qr/If you trust this application/";
     my $moniker = $umech->moniker_for('TestApp::Plugin::OAuth::Action::AuthorizeRequestToken')
         or return "Unable to find moniker for AuthorizeRequestToken";
-    $umech->fill_in_action($moniker, token => $token)
-        or return "Unable to fill in the AuthorizeRequestToken action";
+    $umech->fill_in_action($moniker,
+        token => $token,
+        can_write => $can_write,
+    ) or return "Unable to fill in the AuthorizeRequestToken action";
     $umech->click_button(value => $which_button)
         or return "Unable to click $which_button button";
     return;

Modified: jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/00-test-setup.t
==============================================================================
--- jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/00-test-setup.t	(original)
+++ jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/00-test-setup.t	Sun Mar 16 17:45:40 2008
@@ -4,14 +4,11 @@
 
 use Test::More;
 BEGIN {
-    if (eval { require Net::OAuth::Request; require Crypt::OpenSSL::RSA; 1 }) {
-        unless (eval { Net::OAuth::Request->VERSION('0.05') }) {
-            diag "You might see some test failures if your Net-OAuth isn't 0.05. Please upgrade.";
-        }
+    if (eval { require Net::OAuth::Request; 1 } && eval { Net::OAuth::Request->VERSION('0.05') }) {
         plan tests => 10;
     }
     else {
-        plan skip_all => "Net::OAuth or Crypt::OpenSSL::RSA isn't installed";
+        plan skip_all => "Net::OAuth 0.05 isn't installed";
     }
 }
 
@@ -50,23 +47,27 @@
 is($sig, 'tR3+Ty81lMeYAr/Fid0kMTYa/WM=', 'HMAC-SHA1 signature correct');
 # }}}
 # sign RSA-SHA1 {{{
-($sig, $sbs, $nrp) = sign(
-    'GET',
-    'pfkkdhi9sl3r4s00',
-    'kd94hf93k423kf44',
-    sign_url => 'http://photos.example.net/photos',
-    signature_key => $seckey,
-    oauth_consumer_key => 'dpf43f3p2l4k3l03',
-    oauth_signature_method => 'RSA-SHA1',
-    oauth_timestamp => '1191242096',
-    oauth_nonce => 'kllo9940pd9333jh',
-    oauth_token => 'nnch734d00sl2jdk',
-    file => 'vacation.jpg',
-    size => 'original',
-    oauth_version => '1.0');
+SKIP: {
+    rsa_skip(3);
 
-is($nrp, 'file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=RSA-SHA1&oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original', 'RSA-SHA1 normalized request paramaters correct');
-is($sbs, 'GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DRSA-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal', 'RSA-SHA1 signature-base-string correct');
-is($sig, 'NA2rGBEAnHta9amI/lwEHmuJzkDF2CtfzPNc+jbQIvsFKi0AyRQFi1etC+yxmHLn6bHKSHmn/pR4GOhN+2AP5fi0Aw9mr9n/k7LybUCUwRK/OjJH7b8ESXhkluss+UXCZoLOeaO9Pxskdi1DzWMOhY8si9hfYsCGrHrVbdcqwcw=', 'RSA-SHA1 signature correct');
+    ($sig, $sbs, $nrp) = sign(
+        'GET',
+        'pfkkdhi9sl3r4s00',
+        'kd94hf93k423kf44',
+        sign_url => 'http://photos.example.net/photos',
+        signature_key => $seckey,
+        oauth_consumer_key => 'dpf43f3p2l4k3l03',
+        oauth_signature_method => 'RSA-SHA1',
+        oauth_timestamp => '1191242096',
+        oauth_nonce => 'kllo9940pd9333jh',
+        oauth_token => 'nnch734d00sl2jdk',
+        file => 'vacation.jpg',
+        size => 'original',
+        oauth_version => '1.0');
+
+    is($nrp, 'file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=RSA-SHA1&oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original', 'RSA-SHA1 normalized request paramaters correct');
+    is($sbs, 'GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DRSA-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal', 'RSA-SHA1 signature-base-string correct');
+    is($sig, 'NA2rGBEAnHta9amI/lwEHmuJzkDF2CtfzPNc+jbQIvsFKi0AyRQFi1etC+yxmHLn6bHKSHmn/pR4GOhN+2AP5fi0Aw9mr9n/k7LybUCUwRK/OjJH7b8ESXhkluss+UXCZoLOeaO9Pxskdi1DzWMOhY8si9hfYsCGrHrVbdcqwcw=', 'RSA-SHA1 signature correct');
+}
 # }}}
 

Modified: jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/01-basic.t
==============================================================================
--- jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/01-basic.t	(original)
+++ jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/01-basic.t	Sun Mar 16 17:45:40 2008
@@ -4,11 +4,11 @@
 
 use Test::More;
 BEGIN {
-    if (eval { require Net::OAuth::Request; require Crypt::OpenSSL::RSA; 1 }) {
+    if (eval { require Net::OAuth::Request; 1 } && eval { Net::OAuth::Request->VERSION('0.05') }) {
         plan tests => 9;
     }
     else {
-        plan skip_all => "Net::OAuth or Crypt::OpenSSL::RSA isn't installed";
+        plan skip_all => "Net::OAuth 0.05 isn't installed";
     }
 }
 

Modified: jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/02-request-token.t
==============================================================================
--- jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/02-request-token.t	(original)
+++ jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/02-request-token.t	Sun Mar 16 17:45:40 2008
@@ -4,11 +4,11 @@
 
 use Test::More;
 BEGIN {
-    if (eval { require Net::OAuth::Request; require Crypt::OpenSSL::RSA; 1 }) {
+    if (eval { require Net::OAuth::Request; 1 } && eval { Net::OAuth::Request->VERSION('0.05') }) {
         plan tests => 61;
     }
     else {
-        plan skip_all => "Net::OAuth or Crypt::OpenSSL::RSA isn't installed";
+        plan skip_all => "Net::OAuth 0.05 isn't installed";
     }
 }
 
@@ -60,14 +60,18 @@
 );
 # }}}
 # get a request token as a known consumer (RSA-SHA1) {{{
-response_is(
-    code                   => 200,
-    testname               => "200 - RSA-SHA1 signature",
-    consumer_secret        => 'bar',
-    oauth_consumer_key     => 'foo',
-    signature_key          => $seckey,
-    oauth_signature_method => 'RSA-SHA1',
-);
+SKIP: {
+    rsa_skip(3);
+
+    response_is(
+        code                   => 200,
+        testname               => "200 - RSA-SHA1 signature",
+        consumer_secret        => 'bar',
+        oauth_consumer_key     => 'foo',
+        signature_key          => $seckey,
+        oauth_signature_method => 'RSA-SHA1',
+    );
+};
 # }}}
 # get a request token using authorization header {{{
 response_is(
@@ -83,23 +87,21 @@
 --$timestamp;
 response_is(
     code                   => 200,
-    testname               => "200 - RSA-SHA1 signature",
+    testname               => "200 - same timestamp, different nonce",
     consumer_secret        => 'bar',
     oauth_consumer_key     => 'foo',
     oauth_nonce            => 'kjfh',
-    signature_key          => $seckey,
-    oauth_signature_method => 'RSA-SHA1',
+    oauth_signature_method => 'HMAC-SHA1',
 );
 # }}}
 # same nonce, different timestamp {{{
 response_is(
     code                   => 200,
-    testname               => "200 - RSA-SHA1 signature",
+    testname               => "200 - same nonce, different timestamp",
     consumer_secret        => 'bar',
     oauth_consumer_key     => 'foo',
     oauth_nonce            => 'kjfh',
-    signature_key          => $seckey,
-    oauth_signature_method => 'RSA-SHA1',
+    oauth_signature_method => 'HMAC-SHA1',
 );
 # }}}}
 
@@ -131,14 +133,18 @@
 # failure modes
 
 # request a request token as an RSA-less consumer (RSA-SHA1) {{{
-response_is(
-    code                   => 400,
-    testname               => "400 - RSA-SHA1 signature, without registering RSA key!",
-    consumer_secret        => 'bar2',
-    oauth_consumer_key     => 'foo2',
-    signature_key          => $seckey,
-    oauth_signature_method => 'RSA-SHA1',
-);
+SKIP: {
+    rsa_skip(2);
+
+    response_is(
+        code                   => 400,
+        testname               => "400 - RSA-SHA1 signature, without registering RSA key!",
+        consumer_secret        => 'bar2',
+        oauth_consumer_key     => 'foo2',
+        signature_key          => $seckey,
+        oauth_signature_method => 'RSA-SHA1',
+    );
+};
 # }}}
 # unknown consumer {{{
 # we're back to the first consumer, so we need a locally larger timestamp
@@ -260,8 +266,8 @@
 # }}}
 # GET not POST {{{
 response_is(
-    code                   => 404,
-    testname               => "404 - GET not supported for request_token",
+    code                   => 405,
+    testname               => "405 - GET not allowed for request_token",
     consumer_secret        => 'bar',
     oauth_consumer_key     => 'foo',
     oauth_signature_method => 'PLAINTEXT',

Modified: jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/03-authorize.t
==============================================================================
--- jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/03-authorize.t	(original)
+++ jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/03-authorize.t	Sun Mar 16 17:45:40 2008
@@ -4,11 +4,11 @@
 
 use Test::More;
 BEGIN {
-    if (eval { require Net::OAuth::Request; require Crypt::OpenSSL::RSA; 1 }) {
+    if (eval { require Net::OAuth::Request; 1 } && eval { Net::OAuth::Request->VERSION('0.05') }) {
         plan tests => 85;
     }
     else {
-        plan skip_all => "Net::OAuth or Crypt::OpenSSL::RSA isn't installed";
+        plan skip_all => "Net::OAuth 0.05 isn't installed";
     }
 }
 
@@ -147,7 +147,7 @@
 $umech->form_number(1);
 $umech->click_button(value => 'Deny');
 
-$umech->content_contains("Denying FooBar Industries the right to access your stuff");
+$umech->content_contains("Denying FooBar Industries the right to access your data.");
 $umech->content_contains("click here");
 $umech->content_contains("http://foo.bar.example.com?oauth_token=" . $token_obj->token);
 $umech->content_contains("To return to");
@@ -162,7 +162,7 @@
 $umech->form_number(1);
 $umech->click_button(value => 'Allow');
 
-$umech->content_contains("Allowing FooBar Industries to access your stuff");
+$umech->content_contains("Allowing FooBar Industries to read your data for 1 hour.");
 $umech->content_contains("click here");
 $umech->content_contains("http://foo.bar.example.com?oauth_token=" . $token_obj->token);
 $umech->content_contains("To return to");
@@ -176,7 +176,7 @@
 $umech->fill_in_action_ok($umech->moniker_for('TestApp::Plugin::OAuth::Action::AuthorizeRequestToken'), token => $token_obj->token);
 $umech->click_button(value => 'Deny');
 
-$umech->content_contains("Denying FooBar Industries the right to access your stuff");
+$umech->content_contains("Denying FooBar Industries the right to access your data.");
 $umech->content_contains("click here");
 $umech->content_contains("http://google.com?oauth_token=" . $token_obj->token);
 $umech->content_contains("To return to");
@@ -191,7 +191,7 @@
 $umech->form_number(1);
 $umech->click_button(value => 'Deny');
 
-$umech->content_contains("Denying FooBar Industries the right to access your stuff");
+$umech->content_contains("Denying FooBar Industries the right to access your data.");
 $umech->content_contains("click here");
 my $token = $token_obj->token;
 $umech->content_like(qr{http://google\.com/\?foo=bar&(?:amp;|#38;)?oauth_token=$token});

Modified: jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/04-access-token.t
==============================================================================
--- jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/04-access-token.t	(original)
+++ jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/04-access-token.t	Sun Mar 16 17:45:40 2008
@@ -4,11 +4,11 @@
 
 use Test::More;
 BEGIN {
-    if (eval { require Net::OAuth::Request; require Crypt::OpenSSL::RSA; 1 }) {
+    if (eval { require Net::OAuth::Request; 1 } && eval { Net::OAuth::Request->VERSION('0.05') }) {
         plan tests => 70;
     }
     else {
-        plan skip_all => "Net::OAuth or Crypt::OpenSSL::RSA isn't installed";
+        plan skip_all => "Net::OAuth 0.05 isn't installed";
     }
 }
 

Modified: jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/05-protected-resource.t
==============================================================================
--- jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/05-protected-resource.t	(original)
+++ jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/05-protected-resource.t	Sun Mar 16 17:45:40 2008
@@ -4,11 +4,11 @@
 
 use Test::More;
 BEGIN {
-    if (eval { require Net::OAuth::Request; require Crypt::OpenSSL::RSA; 1 }) {
+    if (eval { require Net::OAuth::Request; 1 } && eval { Net::OAuth::Request->VERSION('0.05') }) {
         plan tests => 58;
     }
     else {
-        plan skip_all => "Net::OAuth or Crypt::OpenSSL::RSA isn't installed";
+        plan skip_all => "Net::OAuth 0.05 isn't installed";
     }
 }
 

Added: jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/06-read-only.t
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/06-read-only.t	Sun Mar 16 17:45:40 2008
@@ -0,0 +1,136 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+use Test::More;
+BEGIN {
+    if (eval { require Net::OAuth::Request; 1 } && eval { Net::OAuth::Request->VERSION('0.05') }) {
+        plan tests => 28;
+    }
+    else {
+        plan skip_all => "Net::OAuth 0.05 isn't installed";
+    }
+}
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use TestApp::Plugin::OAuth::Test;
+
+use Jifty::Test::WWW::Mechanize;
+
+# setup {{{
+# create two consumers {{{
+my $consumer = Jifty::Plugin::OAuth::Model::Consumer->new(current_user => Jifty::CurrentUser->superuser);
+my ($ok, $msg) = $consumer->create(
+    consumer_key => 'foo',
+    secret       => 'bar',
+    name         => 'FooBar Industries',
+    url          => 'http://foo.bar.example.com',
+    rsa_key      => $pubkey,
+);
+ok($ok, $msg);
+
+my $rsaless = Jifty::Plugin::OAuth::Model::Consumer->new(current_user => Jifty::CurrentUser->superuser);
+($ok, $msg) = $rsaless->create(
+    consumer_key => 'foo2',
+    secret       => 'bar2',
+    name         => 'Backwater.org',
+    url          => 'http://backwater.org',
+);
+ok($ok, $msg);
+# }}}
+# create user and log in {{{
+my $u = TestApp::Plugin::OAuth::Model::User->new(current_user => TestApp::Plugin::OAuth::CurrentUser->superuser);
+$u->create( name => 'You Zer', email => 'youzer at example.com', password => 'secret', email_confirmed => 1);
+my $uid = $u->id;
+ok($uid, "New user has valid id set");
+
+$umech->get_ok($URL . '/login');
+$umech->fill_in_action_ok($umech->moniker_for('TestApp::Plugin::OAuth::Action::Login'), email => 'youzer at example.com', password => 'secret');
+$umech->submit;
+$umech->content_contains('Logout');
+# }}}
+# }}}
+# make sure we're not logged in {{{
+response_is(
+    url                    => '/nuke/the/whales',
+    code                   => 200,
+    testname               => "200 - protected resource request",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+    oauth_token            => 'please',
+    token_secret           => 'letmein',
+);
+$cmech->content_contains("Login with a password", "redirected to login");
+$cmech->content_lacks("Press the shiny red button", "did NOT get to a protected page");
+# }}}}
+# REST GET {{{
+get_access_token();
+
+response_is(
+    url                    => "/=/model/User/id/$uid.yml",
+    code                   => 200,
+    method                 => 'GET',
+    testname               => "200 - protected resource request",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+    oauth_token            => $token_obj->token,
+    token_secret           => $token_obj->secret,
+);
+$cmech->content_contains("You Zer", "REST GET works while OAuthed");
+# }}}
+# REST POST {{{
+response_is(
+    url                    => "/=/model/Favorite.yml",
+    thing                  => 'tests',
+    code                   => 200,
+    testname               => "200 - protected resource request",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+    oauth_token            => $token_obj->token,
+    token_secret           => $token_obj->secret,
+);
+
+$cmech->content_like(qr/failure: 1/, "failed to create");
+
+my $favorites = TestApp::Plugin::OAuth::Model::FavoriteCollection->new(
+    current_user => Jifty::CurrentUser->superuser,
+);
+$favorites->unlimit;
+is($favorites->count, 0, "no favorites found");
+# }}}
+# user REST POST {{{
+$umech->post("$URL/=/model/Favorite.yml",
+    { thing => 'more tests' },
+);
+$umech->content_contains("success: 1", "created a favorite");
+
+$favorites = TestApp::Plugin::OAuth::Model::FavoriteCollection->new(
+    current_user => Jifty::CurrentUser->superuser,
+);
+$favorites->unlimit;
+is($favorites->count, 1, "favorite created");
+is($favorites->first->thing, 'more tests', "correct argument");
+# }}}
+# REST DELETE {{{
+response_is(
+    url                    => "/=/model/User/id/$uid.yml!DELETE",
+    code                   => 200,
+    testname               => "200 - protected resource request",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+    oauth_token            => $token_obj->token,
+    token_secret           => $token_obj->secret,
+);
+
+$cmech->content_like(qr/failure: 1/, "failed to delete");
+
+my $user_copy = TestApp::Plugin::OAuth::Model::User->new(current_user => Jifty::CurrentUser->superuser);
+$user_copy->load($uid);
+is($user_copy->name, "You Zer", "REST DELETE doesn't work while the consumer has no write access");
+# }}}

Added: jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/07-read-write.t
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/t/TestApp-Plugin-OAuth/t/07-read-write.t	Sun Mar 16 17:45:40 2008
@@ -0,0 +1,142 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+use Test::More;
+BEGIN {
+    if (eval { require Net::OAuth::Request; 1 } && eval { Net::OAuth::Request->VERSION('0.05') }) {
+        plan tests => 28;
+    }
+    else {
+        plan skip_all => "Net::OAuth 0.05 isn't installed";
+    }
+}
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use TestApp::Plugin::OAuth::Test;
+
+use Jifty::Test::WWW::Mechanize;
+
+# setup {{{
+# create two consumers {{{
+my $consumer = Jifty::Plugin::OAuth::Model::Consumer->new(current_user => Jifty::CurrentUser->superuser);
+my ($ok, $msg) = $consumer->create(
+    consumer_key => 'foo',
+    secret       => 'bar',
+    name         => 'FooBar Industries',
+    url          => 'http://foo.bar.example.com',
+    rsa_key      => $pubkey,
+);
+ok($ok, $msg);
+
+my $rsaless = Jifty::Plugin::OAuth::Model::Consumer->new(current_user => Jifty::CurrentUser->superuser);
+($ok, $msg) = $rsaless->create(
+    consumer_key => 'foo2',
+    secret       => 'bar2',
+    name         => 'Backwater.org',
+    url          => 'http://backwater.org',
+);
+ok($ok, $msg);
+# }}}
+# create user and log in {{{
+my $u = TestApp::Plugin::OAuth::Model::User->new(current_user => TestApp::Plugin::OAuth::CurrentUser->superuser);
+$u->create( name => 'You Zer', email => 'youzer at example.com', password => 'secret', email_confirmed => 1);
+my $uid = $u->id;
+ok($uid, "New user has valid id set");
+
+$umech->get_ok($URL . '/login');
+$umech->fill_in_action_ok($umech->moniker_for('TestApp::Plugin::OAuth::Action::Login'), email => 'youzer at example.com', password => 'secret');
+$umech->submit;
+$umech->content_contains('Logout');
+# }}}
+# }}}
+# make sure we're not logged in {{{
+response_is(
+    url                    => '/nuke/the/whales',
+    code                   => 200,
+    testname               => "200 - protected resource request",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+    oauth_token            => 'please',
+    token_secret           => 'letmein',
+);
+$cmech->content_contains("Login with a password", "redirected to login");
+$cmech->content_lacks("Press the shiny red button", "did NOT get to a protected page");
+# }}}}
+# REST GET {{{
+do {
+    local $TestApp::Plugin::OAuth::Test::can_write = 1;
+    get_access_token();
+};
+
+response_is(
+    url                    => "/=/model/User/id/$uid.yml",
+    code                   => 200,
+    method                 => 'GET',
+    testname               => "200 - protected resource request",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+    oauth_token            => $token_obj->token,
+    token_secret           => $token_obj->secret,
+);
+$cmech->content_contains("You Zer", "REST GET works while OAuthed");
+# }}}
+# REST POST {{{
+response_is(
+    url                    => "/=/model/Favorite.yml",
+    thing                  => 'tests',
+    code                   => 200,
+    testname               => "200 - protected resource request",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+    oauth_token            => $token_obj->token,
+    token_secret           => $token_obj->secret,
+);
+
+$cmech->content_unlike(qr/failure: 1/, "created");
+
+my $favorites = TestApp::Plugin::OAuth::Model::FavoriteCollection->new(
+    current_user => Jifty::CurrentUser->superuser,
+);
+$favorites->unlimit;
+is($favorites->count, 1, "no favorites found");
+is($favorites->first->thing, 'tests', "correct argument");
+# }}}
+# user REST POST {{{
+$umech->post("$URL/=/model/Favorite.yml",
+    { thing => 'more tests' },
+);
+$umech->content_contains("success: 1", "created a favorite");
+
+$favorites = TestApp::Plugin::OAuth::Model::FavoriteCollection->new(
+    current_user => Jifty::CurrentUser->superuser,
+);
+$favorites->unlimit;
+is($favorites->count, 2, "favorite created");
+# }}}
+# REST DELETE {{{
+response_is(
+    url                    => "/=/model/User/id/$uid.yml!DELETE",
+    code                   => 200,
+    testname               => "200 - protected resource request",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+    oauth_token            => $token_obj->token,
+    token_secret           => $token_obj->secret,
+);
+
+$cmech->content_unlike(qr/failure: 1/, "failed to delete");
+
+Jifty::Record->flush_cache if Jifty::Record->can('flush_cache');
+
+my $user_copy = TestApp::Plugin::OAuth::Model::User->new(current_user => Jifty::CurrentUser->superuser);
+$user_copy->load($uid);
+is($user_copy->name, undef, "REST DELETE works while consumer has write access");
+# }}}
+

Modified: jifty/branches/jquery/t/TestApp-Plugin-OnClick/Makefile.PL
==============================================================================
--- jifty/branches/jquery/t/TestApp-Plugin-OnClick/Makefile.PL	(original)
+++ jifty/branches/jquery/t/TestApp-Plugin-OnClick/Makefile.PL	Sun Mar 16 17:45:40 2008
@@ -1,6 +1,6 @@
 use inc::Module::Install;
 
-name        'TestApp::Plugin::OnClick';
+name        'TestApp-Plugin-OnClick';
 version     '0.01';
 requires    'Jifty' => '0.70824';
 

Modified: jifty/branches/jquery/t/TestApp-Plugin-PasswordAuth/Makefile.PL
==============================================================================
--- jifty/branches/jquery/t/TestApp-Plugin-PasswordAuth/Makefile.PL	(original)
+++ jifty/branches/jquery/t/TestApp-Plugin-PasswordAuth/Makefile.PL	Sun Mar 16 17:45:40 2008
@@ -1,6 +1,6 @@
 use inc::Module::Install;
 
-name        'TestApp::Plugin::PasswordAuth';
+name        'TestApp-Plugin-PasswordAuth';
 version     '0.01';
 requires    'Jifty' => '0.70117';
 

Modified: jifty/branches/jquery/t/TestApp-Plugin-SinglePage/Makefile.PL
==============================================================================
--- jifty/branches/jquery/t/TestApp-Plugin-SinglePage/Makefile.PL	(original)
+++ jifty/branches/jquery/t/TestApp-Plugin-SinglePage/Makefile.PL	Sun Mar 16 17:45:40 2008
@@ -1,6 +1,6 @@
 use inc::Module::Install;
 
-name        'TestApp::Plugin::JQuery';
+name        'TestApp-Plugin-JQuery';
 version     '0.01';
 requires    'Jifty' => '0.70129';
 

Modified: jifty/branches/jquery/t/TestApp/t/15-template-subclass.t
==============================================================================
--- jifty/branches/jquery/t/TestApp/t/15-template-subclass.t	(original)
+++ jifty/branches/jquery/t/TestApp/t/15-template-subclass.t	Sun Mar 16 17:45:40 2008
@@ -58,7 +58,7 @@
 
 sub in_region {
     qq|<script type="text/javascript">
-new Region('$_[0]',{},'$_[1]',null);
+new Region('$_[0]',{},'$_[1]',null,null);
 </script><div id="region-$_[0]" class="jifty-region">$_[2]</div>|;
 }
 

Modified: jifty/branches/jquery/t/TestApp/t/16-template-region.t
==============================================================================
--- jifty/branches/jquery/t/TestApp/t/16-template-region.t	(original)
+++ jifty/branches/jquery/t/TestApp/t/16-template-region.t	Sun Mar 16 17:45:40 2008
@@ -13,7 +13,7 @@
         text => q|list!
 <span>1</span>
 <span>2</span><script type="text/javascript">
-new Region('special',{'id':3},'/foo/item',null);
+new Region('special',{'id':3},'/foo/item',null,null);
 </script><div id="region-special" class="jifty-region">
 <span>3</span></div>|
     },

Added: jifty/branches/jquery/utils/js_size.pl
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/utils/js_size.pl	Sun Mar 16 17:45:40 2008
@@ -0,0 +1,60 @@
+#!/usr/bin/perl
+use strict;
+use IPC::Run3 'run3';
+use Jifty;
+Jifty->new;
+
+=head1 NAME
+
+js_size - summarize the size of javascript used in your jifty app
+
+=head1 SYNOPSIS
+
+  % perl js_size.pl
+
+  % perl js_size.pl /path/to/jsmin
+
+=head1 DESCRIPTION
+
+
+
+=cut
+
+my $jsmin = shift ;
+
+my $static_handler = Jifty->handler->view('Jifty::View::Static::Handler');
+my $js = "";
+
+
+
+my $total = 0;
+my $total_minified = 0;
+my $size = {};
+my $size_minified = {};
+for my $file ( @{ Jifty::Web->javascript_libs } ) {
+    my $include = $static_handler->file_path( File::Spec->catdir( 'js', $file ) );
+    $total += $size->{$file} = -s $include;
+
+    if ($jsmin) {
+        my $output = '';
+        open my $input, '<', $include;
+        run3 [$jsmin], $input, \$output, undef;
+        $total_minified += $size_minified->{$file} = length $output;
+    }
+}
+
+for my $file ( @{ Jifty::Web->javascript_libs } ) {
+    if ($jsmin) {
+        printf("%5.2f%% (%5.2f%%) %7d (%7d) $file\n",
+           ($size->{$file} / $total * 100),
+           ($size_minified->{$file} / $total_minified * 100),
+           $size->{$file}, $size_minified->{$file}, $file );
+    }
+    else {
+        printf("%5.2f%% %7d $file\n",
+           ($size->{$file} / $total * 100),
+           $size->{$file}, $file );
+    }
+}
+
+#    


More information about the Jifty-commit mailing list