allow article custom fields to be specified by config
authorTony Cook <tony@develop-help.com>
Tue, 13 Nov 2012 05:26:06 +0000 (16:26 +1100)
committerTony Cook <tony@develop-help.com>
Tue, 13 Nov 2012 05:26:06 +0000 (16:26 +1100)
13 files changed:
MANIFEST
site/cgi-bin/modules/BSE/Edit/Article.pm
site/cgi-bin/modules/BSE/Edit/Catalog.pm
site/cgi-bin/modules/BSE/Edit/Product.pm
site/cgi-bin/modules/BSE/Request/Base.pm
site/cgi-bin/modules/DevHelp/Validate.pm
site/cgi-bin/modules/Squirrel/Row.pm
site/docs/config.pod
site/templates/admin/edit_1.tmpl
site/templates/admin/edit_catalog.tmpl
site/templates/admin/edit_product.tmpl
site/templates/admin/include/article_cfg_custom.tmpl [new file with mode: 0644]
site/templates/preload.tmpl

index 7e16e05aebeadcf4a5b06cfec7438b98b0f8908a..aaf68cae308229cdc6ea5c1d7deeadb2231b67a4 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -566,6 +566,7 @@ site/templates/admin/imageclean/intro.tmpl
 site/templates/admin/imageclean/preview.tmpl
 site/templates/admin/import/import.tmpl
 site/templates/admin/import/start.tmpl
+site/templates/admin/include/article_cfg_custom.tmpl
 site/templates/admin/include/article_menu.tmpl
 site/templates/admin/include/auditentry.tmpl
 site/templates/admin/include/audithead.tmpl
index b0dfccb7679cdc1617b48b2278284c313695f4e3..9a82daf8be068fea0b4092e268fbbcb98f1489aa 100644 (file)
@@ -14,8 +14,9 @@ use BSE::Regen 'generate_article';
 use DevHelp::Date qw(dh_parse_date dh_parse_sql_date);
 use List::Util qw(first);
 use constant MAX_FILE_DISPLAYNAME_LENGTH => 255;
+use constant ARTICLE_CUSTOM_FIELDS_CFG => "article custom fields";
 
-our $VERSION = "1.029";
+our $VERSION = "1.030";
 
 =head1 NAME
 
@@ -1153,6 +1154,76 @@ sub iter_tags {
   return $article->tag_objects;
 }
 
+my %base_custom_validation =
+  (
+   customDate1 =>
+   {
+    rules => "date",
+    htmltype => "text",
+    width => 10,
+    default => "",
+   },
+   customDate2 =>
+   {
+    rules => "date",
+    htmltype => "text",
+    width => 10,
+    default => "",
+   },
+   customStr1 =>
+   {
+    htmltype => "text",
+    default => "",
+   },
+   customStr2 =>
+   {
+    htmltype => "text",
+    default => "",
+   },
+   customInt1 =>
+   {
+    rules => "integer",
+    htmltype => "text",
+    width => 10,
+    default => "",
+   },
+   customInt2 =>
+   {
+    rules => "integer",
+    htmltype => "text",
+    width => 10,
+    default => "",
+   },
+   customInt3 =>
+   {
+    rules => "integer",
+    htmltype => "text",
+    width => 10,
+    default => "",
+   },
+   customInt4 =>
+   {
+    rules => "integer",
+    htmltype => "text",
+    width => 10,
+    default => "",
+   },
+  );
+
+sub custom_fields {
+  my $self = shift;
+
+  require DevHelp::Validate;
+  DevHelp::Validate->import;
+  return DevHelp::Validate::dh_configure_fields
+    (
+     \%base_custom_validation,
+     $self->cfg,
+     ARTICLE_CUSTOM_FIELDS_CFG,
+     BSE::DB->single->dbh,
+    );
+}
+
 sub low_edit_tags {
   my ($self, $acts, $request, $article, $articles, $msg, $errors) = @_;
 
@@ -1200,6 +1271,10 @@ sub low_edit_tags {
   my $it = BSE::Util::Iterate->new;
   my $ito = BSE::Util::Iterate::Objects->new;
   my $ita = BSE::Util::Iterate::Article->new(req => $request);
+
+  my $custom = $self->custom_fields;
+  $request->set_variable(custom => $custom);
+
   return
     (
      $request->admin_tags,
@@ -1514,6 +1589,15 @@ sub _validate_common {
       $errors->{category} = "msg:bse/admin/edit/category/unknown";
     }
   }
+
+  require DevHelp::Validate;
+  DevHelp::Validate->import('dh_validate_hash');
+  dh_validate_hash($data, $errors,
+                  {
+                   fields => $self->custom_fields,
+                   optional => 1,
+                  },
+                  $self->cfg, ARTICLE_CUSTOM_FIELDS_CFG);
 }
 
 sub validate {
@@ -1565,6 +1649,32 @@ sub validate_parent {
 sub fill_new_data {
   my ($self, $req, $data, $articles) = @_;
 
+  my $custom = $self->custom_fields;
+  for my $key (keys %$custom) {
+    my ($value) = $req->cgi->param($key);
+    if ($custom->{$key}{description} && defined $value) {
+      if ($key =~ /^customDate/) {
+       if ($value =~ /^\s*([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)\s*$/) {
+         $data->{$key} = "$3-$2-$1";
+       }
+       else {
+         $data->{$key} = undef;
+       }
+      }
+      elsif ($key =~ /^customInt/) {
+       if ($value =~ /\S/) {
+         $data->{$key} = $value;
+       }
+       else {
+         $data->{$key} = undef;
+       }
+      }
+      else {
+       $data->{$key} = $value;
+      }
+    }
+  }
+
   custom_class($self->{cfg})
     ->article_fill_new($data, $self->typename);
 
@@ -1853,6 +1963,30 @@ sub fill_old_data {
     $article->{$col} = $data->{$col}
       if exists $data->{$col} && $col ne 'id' && $col ne 'parentid';
   }
+  my $custom = $self->custom_fields;
+  for my $key (keys %$custom) {
+    if ($custom->{$key}{description} && exists $data->{$key}) {
+      if ($key =~ /^customDate/) {
+       if ($data->{key} =~ /^\s*([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)\s*$/) {
+         $article->set($key => "$3-$2-$1");
+       }
+       else {
+         $article->set($key => undef);
+       }
+      }
+      elsif ($key =~ /^customInt/) {
+       if ($data->{$key} =~ /\S/) {
+         $article->set($key => $data->{$key});
+       }
+       else {
+         $article->set($key => undef);
+       }
+      }
+      else {
+       $article->set($key => $data->{$key});
+      }
+    }
+  }
   custom_class($self->{cfg})
     ->article_fill_old($article, $data, $self->typename);
 
index 79b490c74c4be8a89704a5809b5d1b0eba27f019..79de43c7bb7b98a5031118e3759e6eb381f3e035 100644 (file)
@@ -3,8 +3,9 @@ use strict;
 use base 'BSE::Edit::Article';
 use BSE::Util::HTML;
 use BSE::Util::Tags qw(tag_article);
+use constant CATALOG_CUSTOM_FIELDS_CFG => "catalog custom fields";
 
-our $VERSION = "1.002";
+our $VERSION = "1.003";
 
 sub base_template_dirs {
   return ( "catalog" );
@@ -140,5 +141,21 @@ sub type_default_value {
   return $self->SUPER::type_default_value($req, $col);
 }
 
+sub custom_fields {
+  my ($self) = @_;
+
+  my $custom = $self->SUPER::custom_fields();
+
+  require DevHelp::Validate;
+  DevHelp::Validate->import;
+  return DevHelp::Validate::dh_configure_fields
+    (
+     $custom,
+     $self->cfg,
+     CATALOG_CUSTOM_FIELDS_CFG,
+     BSE::DB->single->dbh,
+    );
+}
+
 1;
 
index 0927c3c148a8a879a9b0b1b591de2ba0174b05d3..90033334f8f0e40c868cb980d7b671b0a5a24700 100644 (file)
@@ -8,8 +8,9 @@ use BSE::Util::Iterate;
 use BSE::Util::HTML;
 use BSE::CfgInfo 'product_options';
 use BSE::Util::Tags qw(tag_hash tag_article);
+use constant PRODUCT_CUSTOM_FIELDS_CFG => "product custom fields";
 
-our $VERSION = "1.010";
+our $VERSION = "1.011";
 
 =head1 NAME
 
@@ -1822,6 +1823,22 @@ sub req_option_value_reorder {
   return $self->refresh($article, $req->cgi, undef, "Values reordered");
 }
 
+sub custom_fields {
+  my ($self) = @_;
+
+  my $custom = $self->SUPER::custom_fields();
+
+  require DevHelp::Validate;
+  DevHelp::Validate->import;
+  return DevHelp::Validate::dh_configure_fields
+    (
+     $custom,
+     $self->cfg,
+     PRODUCT_CUSTOM_FIELDS_CFG,
+     BSE::DB->single->dbh,
+    );
+}
+
 sub article_actions {
   my $self = shift;
 
index 1a7f0ca7f72aed930c9c5c6638459174eb1415be..3ed1e8e57d98e603c39a8390148443cd1e69bf91 100644 (file)
@@ -5,7 +5,7 @@ use BSE::Cfg;
 use BSE::Util::HTML;
 use Carp qw(cluck confess);
 
-our $VERSION = "1.014";
+our $VERSION = "1.015";
 
 sub new {
   my ($class, %opts) = @_;
@@ -409,6 +409,7 @@ sub messages {
          };
       }
     }
+    $self->{field_errors} = $errors;
   }
   elsif ($errors && !ref $errors) {
     push @messages,
@@ -450,6 +451,19 @@ sub message {
     map { $_->{type} eq 'html' ? $_->{text} : escape_html($_->{text}) } @$messages
 }
 
+=item field_errors
+
+Return a hash of field errors that have been supplied to
+message()/messages().
+
+=cut
+
+sub field_errors {
+  my ($self) = @_;
+
+  return $self->{field_errors} || {};
+}
+
 sub _set_vars {
   my ($self) = @_;
 
index 67f815e0cf6dbe4f956f09d03e832c4f90f6a1d8..be2b4b6dfa94eddb332733ffcaae0bb442a24d25 100644 (file)
@@ -6,7 +6,7 @@ use vars qw(@EXPORT_OK @ISA);
 @ISA = qw(Exporter);
 use Carp qw(confess);
 
-our $VERSION = "1.001";
+our $VERSION = "1.002";
 
 my %built_ins =
   (
@@ -125,6 +125,10 @@ my %built_ins =
     maxdate => '+1d',
     maxdatemsg => 'The date entered must be in the past',
    },
+   integer =>
+   {
+    integer => 1,
+   },
    natural => 
    {
     integer => '0-', # 0 or higher
@@ -621,10 +625,13 @@ sub _get_cfg_fields {
 
   my $fields = $cfg->entry($section, 'fields', '');
   my @names = ( split(/,/, $fields), keys %$field_hash );
+  my @extra_config;
+  push @extra_config, split /,/, $cfg->entry("form validation", "field_config", "");
+  push @extra_config, split /,/, $cfg->entry($section, "field_config", "");
 
   for my $field (@names) {
     $cfg_fields->{$field} = {};
-    for my $cfg_name (qw(required rules description required_error range_error mindatemsg maxdatemsg ne_error)) {
+    for my $cfg_name (qw(required rules description required_error range_error mindatemsg maxdatemsg ne_error), @extra_config) {
       my $value = $cfg->entry($section, "${field}_$cfg_name");
       if (defined $value) {
        $cfg_fields->{$field}{$cfg_name} = $value;
@@ -975,6 +982,10 @@ past.
 
 A valid date in the future.
 
+=item integer
+
+Any integer.
+
 =item natural
 
 An integer greater or equal to zero.
index 65d09ac1615a8be5249d762d723f8978169ec740..f920ab5ec1c4be0446d6dd4590aad4ecb36b817a 100644 (file)
@@ -2,7 +2,7 @@ package Squirrel::Row;
 require 5.005;
 use strict;
 
-our $VERSION = "1.003";
+our $VERSION = "1.004";
 
 use Carp;
 use BSE::DB;
@@ -212,6 +212,15 @@ sub set {
   return $value;
 }
 
+sub get {
+  my ($self, $name) = @_;
+
+  exists $self->{$name}
+    or do { warn "Attempt to get unknown column '$name' in ",ref $self; return };
+
+  return $self->{$name};
+}
+
 use vars '$AUTOLOAD';
 
 sub AUTOLOAD {
index 0c48830a6cbceded313fb7dd1bbb6102da76ae27..40980e7fbeadf8b256fbae75d25457c50267ac6a 100644 (file)
@@ -2723,6 +2723,23 @@ Entries are sorted by value.
 If the value contains a comma the text up to the comma is removed and
 the remains used as the label, otherwise the key is used as the label.
 
+=head2 [article custom fields]
+
+=head2 [product custom fields]
+
+=head2 [catalog custom fields]
+
+These can be used to define article, product and catalog custom fields
+respectively, in a similar manner to L<formmail>.
+
+The field names are predefined, C<customDate1>, C<customDate2>,
+C<customInt1>, C<customInt2>, C<customInt3>, C<customInt4>,
+C<customStr1> and C<customStr2>, but are only active if a description
+is defined for the field:
+
+  [article custom fields]
+  customInt1_description=Stock Level
+
 =head1 AUTHOR
 
 Tony Cook <tony@develop-help.com>
index 53c00777b9866d7885d2782b16da98c8fe6ff130..fe23870bcb347ef91080f7f50fe29996ee15d8b8 100644 (file)
           </tr>
 <:include admin/include/edit_common.tmpl:>
 <:include admin/article_custom.tmpl optional:>
+<:include admin/include/article_cfg_custom.tmpl:>
           <tr> 
             <th>Thumbnail image:</th>
             <td> 
index b3ea3598571e8e4b30290690cd1010db15911096..453e251ac57cbd6a518e5404dde8387a76c3bff3 100644 (file)
           </tr>
 <:include admin/include/edit_common.tmpl:>
 <:include admin/catalog_custom.tmpl optional:>
+<:include admin/include/article_cfg_custom.tmpl -:>
           <tr> 
             <th>Thumbnail image:</th>
             <td> 
index 4a7a66ef64ff4d49d790e58bf46129ea4d7a83e3..058933e898d051250f0dc6f5e9eec5dca91a0d86 100644 (file)
               (<:alloptions:>)<:or:><:product options:><:eif:> </td>
             <td class="help"><:help product options:> <:error_img options:></td>
           </tr>
-<:include admin/product_custom.tmpl optional:><:include admin/include/custom/product_custom.tmpl optional:><:include admin/include/product_custom.tmpl optional:>
+<:include admin/product_custom.tmpl optional -:>
+<:include admin/include/custom/product_custom.tmpl optional -:>
+<:include admin/include/product_custom.tmpl optional -:>
+<:include admin/include/article_cfg_custom.tmpl -:>
           <tr> 
             <th>Thumbnail image:</th>
             <td> 
diff --git a/site/templates/admin/include/article_cfg_custom.tmpl b/site/templates/admin/include/article_cfg_custom.tmpl
new file mode 100644 (file)
index 0000000..089e392
--- /dev/null
@@ -0,0 +1,13 @@
+<:.for f in custom.list -:>
+<:.if f.value.description ne "" -:>
+<tr>
+   <th><:= f.value.description | html :>:</th>
+   <td>
+     <input type="text" name="<:= f.key | html :>" value="<:.call "old", "field":f.key, "default": ifnew ? f.value.default : article.get(f.key) :>" />
+   </td>
+   <td class="help">
+     <:-.call "error_img", "field":f.key -:>
+   </td>
+</tr>
+<:  .end if -:>
+<:.end for -:>
\ No newline at end of file
index e44796defeccad6d8c43cac6bb79e2002e0571ab..4ad68ae372a64832a29a170dae20695aa02a2e41 100644 (file)
@@ -44,4 +44,19 @@ Page <:= pages.page :> of <:= pages.pagecount :>
 <span>&gt</span>
 <:.end if -:>
 </div>
-<:-.end define -:>
\ No newline at end of file
+<:-.end define -:>
+
+<:.define old -:>
+<:# parameters: field, default -:>
+<:  .if cgi.param(field).defined -:>
+<:= cgi.param(field) -:>
+<:  .else -:>
+<:= default | html -:>
+<:  .end if -:>
+<:.end define-:>
+
+<:.define error_img -:>
+<:# parameters: field -:>
+<:# this implementation should probably change -:>
+<:= ("error_img " _ field).evaltag -:>
+<:.end if -:>