update the XLS importer to handle attaching images to articles
authorTony Cook <tony@develop-help.com>
Tue, 5 May 2009 13:42:34 +0000 (13:42 +0000)
committertony <tony@45cb6cf1-00bc-42d2-bb5a-07f51df49f94>
Tue, 5 May 2009 13:42:34 +0000 (13:42 +0000)
site/cgi-bin/modules/Article.pm
site/cgi-bin/modules/BSE/API.pm
site/cgi-bin/modules/BSE/Edit/Article.pm
site/cgi-bin/modules/BSE/ProductImportXLS.pm
site/util/bsexlsprod.pl

index d2d3b54..10bbe7c 100644 (file)
@@ -232,18 +232,32 @@ sub cached_filename {
   return $dynamic_path . "/" . $self->{id} . ".html";
 }
 
-sub remove {
+sub remove_images {
   my ($self, $cfg) = @_;
 
-  $cfg or confess "No \$cfg supplied to ", ref $self, "->remove";
-
-  require Images;
-  my @images = Images->getBy(articleId=>$self->{id});
+  my @images = $self->images;
+  my $mgr;
   my $imagedir = $cfg->entry('paths', 'images', $Constants::IMAGEDIR);
   for my $image (@images) {
+    if ($image->{storage} ne 'local') {
+      unless ($mgr) {
+       require BSE::StorageMgr::Images;
+       $mgr = BSE::StorageMgr::Images->new(cfg => $cfg);
+      }
+      $mgr->unstore($image->{image}, $image->{storage});
+    }
+
     unlink("$imagedir/$image->{image}");
     $image->remove();
   }
+}
+
+sub remove {
+  my ($self, $cfg) = @_;
+
+  $cfg or confess "No \$cfg supplied to ", ref $self, "->remove";
+
+  $self->remove_images($cfg);
 
   for my $file ($self->files) {
     $file->remove($cfg);
index 00bfddf..52ec83d 100644 (file)
@@ -5,8 +5,8 @@ use BSE::Util::SQL qw(sql_datetime now_sqldatetime);
 use BSE::Cfg;
 require Exporter;
 @ISA = qw(Exporter);
-@EXPORT_OK = qw(bse_cfg bse_make_product bse_make_catalog bse_encoding);
-use Carp qw(confess);
+@EXPORT_OK = qw(bse_cfg bse_make_product bse_make_catalog bse_encoding bse_add_image);
+use Carp qw(confess croak);
 
 my %acticle_defaults =
   (
@@ -214,4 +214,37 @@ sub bse_encoding {
   return $cfg->entry('html', 'charset', 'iso-8859-1');
 }
 
+sub bse_add_image {
+  my ($cfg, $article, %opts) = @_;
+
+  my $editor;
+  ($editor, $article) = _load_editor_class($article, $cfg);
+
+  my %image;
+  my $file = delete $opts{file};
+  $file
+    or croak "Missing image filename";
+  open IN, "< $file"
+    or croak "Failed opening image file $file: $!";
+  binmode IN;
+  my %errors;
+
+  $editor->do_add_image
+    (
+     $cfg,
+     $article,
+     *IN,
+     %opts,
+     errors => \%errors,
+     filename => $file,
+    );
+}
+
+sub _load_editor_class {
+  my ($article, $cfg) = @_;
+
+  require BSE::Edit::Base;
+  return BSE::Edit::Base->article_class($article, 'Articles', $cfg);
+}
+
 1;
index 183c295..de114cf 100644 (file)
@@ -2387,7 +2387,7 @@ sub save_image_changes {
 }
 
 sub _service_error {
-  my ($self, $req, $article, $articles, $error) = @_;
+  my ($self, $req, $article, $articles, $msg, $error) = @_;
 
   if ($req->cgi->param('_service')) {
     my $body = '';
@@ -2412,7 +2412,7 @@ sub _service_error {
       };
   }
   else {
-    return $self->edit_form($req, $article, $articles, $error);
+    return $self->edit_form($req, $article, $articles, $msg, $error);
   }
 }
 
@@ -2430,59 +2430,52 @@ sub _service_success {
     };
 }
 
-sub add_image {
-  my ($self, $req, $article, $articles) = @_;
-
-  $req->user_can(edit_images_add => $article)
-    or return $self->_service_error($req, $article, $articles,
-                                   "You don't have access to add new images to this article");
+sub do_add_image {
+  my ($self, $cfg, $article, $image, %opts) = @_;
 
-  my $cgi = $req->cgi;
+  my $errors = $opts{errors}
+    or die "No errors parameter";
 
-  my %errors;
-  my $msg;
-  my $imageref = $cgi->param('name');
+  my $imageref = $opts{name};
   if (defined $imageref && $imageref ne '') {
     if ($imageref =~ /^[a-z_]\w+$/i) {
       # make sure it's unique
       my @images = $self->get_images($article);
       for my $img (@images) {
        if (defined $img->{name} && lc $img->{name} eq lc $imageref) {
-         $errors{name} = 'Image name must be unique to the article';
+         $errors->{name} = 'Image name must be unique to the article';
          last;
        }
       }
     }
     else {
-      $errors{name} = 'Image name must be empty or alphanumeric beginning with an alpha character';
+      $errors->{name} = 'Image name must be empty or alphanumeric beginning with an alpha character';
     }
   }
   else {
     $imageref = '';
   }
-  unless ($errors{name}) {
+  unless ($errors->{name}) {
     my $workmsg;
     $self->validate_image_name($imageref, \$workmsg)
-      or $errors{name} = $workmsg;
+      or $errors->{name} = $workmsg;
   }
 
-  my $image = $cgi->param('image');
   if ($image) {
     if (-z $image) {
-      $errors{image} = 'Image file is empty';
+      $errors->{image} = 'Image file is empty';
     }
   }
   else {
-    #$msg = 'Enter or select the name of an image file on your machine';
-    $errors{image} = 'Please enter an image filename';
-  }
-  if ($msg || keys %errors) {
-    return $self->_service_error($req, $article, $articles, $msg, \%errors);
+    $errors->{image} = 'Please enter an image filename';
   }
+  keys %$errors
+    and return;
 
-  my $imagename = $image;
+  my $imagename = $opts{filename} || $image;
   $imagename .= ''; # force it into a string
   my $basename = '';
+  $imagename =~ tr/ //d;
   $imagename =~ /([\w.-]+)$/ and $basename = $1;
 
   # create a filename that we hope is unique
@@ -2491,7 +2484,7 @@ sub add_image {
   # for the sysopen() constants
   use Fcntl;
 
-  my $imagedir = cfg_image_dir($req->cfg);
+  my $imagedir = cfg_image_dir($cfg);
   # loop until we have a unique filename
   my $counter="";
   $filename = time. '_' . $counter . '_' . $basename 
@@ -2519,9 +2512,9 @@ sub add_image {
 
   my($width,$height) = imgsize("$imagedir/$filename");
 
-  my $alt = $cgi->param('altIn');
+  my $alt = $opts{alt};
   defined $alt or $alt = '';
-  my $url = $cgi->param('url');
+  my $url = $opts{url};
   defined $url or $url = '';
   my %image =
     (
@@ -2541,9 +2534,9 @@ sub add_image {
   shift @cols;
   my $imageobj = Images->add(@image{@cols});
 
-  my $storage = $cgi->param('storage');
+  my $storage = $opts{storage};
   defined $storage or $storage = 'local';
-  my $image_manager = $self->_image_manager($req->cfg);
+  my $image_manager = $self->_image_manager($cfg);
   local $SIG{__DIE__};
   eval {
     my $src;
@@ -2557,9 +2550,41 @@ sub add_image {
     }
   };
   if ($@) {
-    $req->flash($@);
+    $errors->{flash} = $@;
   }
 
+  return $imageobj;
+}
+
+sub add_image {
+  my ($self, $req, $article, $articles) = @_;
+
+  $req->user_can(edit_images_add => $article)
+    or return $self->_service_error($req, $article, $articles,
+                                   "You don't have access to add new images to this article");
+
+  my $cgi = $req->cgi;
+  my %errors;
+  my $imageobj =
+    $self->do_add_image
+      (
+       $req->cfg,
+       $article,
+       scalar($cgi->param('image')),
+       name => scalar($cgi->param('name')),
+       alt => scalar($cgi->param('altIn')),
+       url => scalar($cgi->param('url')),
+       storage => scalar($cgi->param('storage')),
+       errors => \%errors,
+      );
+
+  $imageobj
+    or return $self->_service_error($req, $article, $articles, undef, \%errors);
+
+  # typically a soft failure from the storage
+  $errors{flash}
+    and $req->flash($errors{flash});
+
   use Util 'generate_article';
   generate_article($articles, $article) if $Constants::AUTO_GENERATE;
 
index 9b6c0fa..71342d4 100644 (file)
@@ -1,12 +1,13 @@
 package BSE::ProductImportXLS;
 use strict;
 use Spreadsheet::ParseExcel;
-use BSE::API qw(bse_make_product bse_make_catalog);
+use BSE::API qw(bse_make_product bse_make_catalog bse_add_image);
 use Articles;
 use Products;
+use Config;
 
 sub new {
-  my ($class, $cfg, $profile) = @_;
+  my ($class, $cfg, $profile, %opts) = @_;
 
   # field mapping
   my $section = "xls import $profile";
@@ -19,6 +20,18 @@ sub new {
   my $use_codes = $cfg->entry($section, 'codes', 0);
   my $parent = $cfg->entry($section, 'parent', 3);
   my $price_dollar = $cfg->entry($section, 'price_dollar', 0);
+  my $reset_images = $cfg->entry($section, 'reset_images', 0);
+  my $file_path = $cfg->entry($section, 'file_path');
+  defined $file_path or $file_path = '';
+  my @file_path = split /$Config{path_sep}/, $file_path;
+  if ($opts{file_path}) {
+    unshift @file_path, 
+      map 
+       { 
+         split /$Config{path_sep}/, $_ 
+       }
+         @{$opts{file_path}};
+  }
 
   my %map;
   for my $map (grep /^map_\w+$/, keys %ids) {
@@ -28,12 +41,17 @@ sub new {
       or die "Mapping for $out not numeric\n";
     $map{$out} = $in;
   }
+  my %set;
+  for my $set (grep /^set_\w+$/, keys %ids) {
+    (my $out = $set) =~ s/^set_//;
+    $set{$out} = $ids{$set};
+  }
   my %xform;
   for my $xform (grep /^xform_\w+$/, keys %ids) {
     (my $out = $xform) =~ s/^xform_//;
     $map{$out}
       or die "Xform for $out but no mapping\n";
-    my $code = "sub { local (\$_) = \@_; \n".$ids{$xform}."\n; return \$_ }";
+    my $code = "sub { (local \$_, my \$product) = \@_; \n".$ids{$xform}."\n; return \$_ }";
     my $sub = eval $code;
     $sub
       or die "Compilation error for $xform code: $@\n";
@@ -56,13 +74,16 @@ sub new {
     {
      map => \%map,
      xform => \%xform,
+     set => \%set,
      sheet => $sheet,
      skiprows => $skiprows,
      codes => $use_codes,
      cats => \@cats,
      parent => $parent,
      price_dollar => $price_dollar,
+     reset_images => $reset_images,
      cfg => $cfg,
+     file_path => \@file_path,
      product_template => scalar($cfg->entry($section, 'product_template')),
      catalog_template => scalar($cfg->entry($section, 'catalog_template')),
     }, $class;
@@ -94,7 +115,7 @@ sub process {
   my %cat_cache;
   for my $rownum ($self->{skiprows} ... $maxrow) {
     eval {
-      my %entry;
+      my %entry = %{$self->{set}};
 
       $self->{product_template}
        and $entry{template} = $self->{product_template};
@@ -103,10 +124,9 @@ sub process {
       for my $col (keys %{$self->{map}}) {
        my $cell = $ws->get_cell($rownum, $self->{map}{$col}-1);
        $entry{$col} = $cell->value;
-
-       if ($self->{xform}{$col}) {
-         $entry{$col} = $self->{xform}{$col}->($entry{$col});
-       }
+      }
+      for my $col (keys %{$self->{xform}}) {
+       $entry{$col} = $self->{xform}{$col}->($entry{$col}, \%entry);
       }
       $entry{title} =~ /\S/
        or die "title blank\n";
@@ -143,6 +163,11 @@ sub process {
        $product->save;
        $callback
          and $callback->("Updated $product->{id}: $entry{title}");
+       if ($self->{reset_images}) {
+         $product->remove_images($self->{cfg});
+         $callback
+           and $callback->(" $product->{id}: Reset images");
+       }
       }
       else
       {
@@ -154,6 +179,29 @@ sub process {
        $callback
          and $callback->("Added $product->{id}: $entry{title}");
       }
+      for my $image_index (1 .. 10) {
+       my $file = $entry{"image${image_index}_file"};
+       $file
+         or next;
+       my $full_file = $self->_find_file($file);
+       $full_file
+         or die "File '$file' not found for image$image_index\n";
+
+       my %opts = ( file => $full_file );
+       for my $key (qw/alt name url storage/) {
+         my $fkey = "image${image_index}_$key";
+         $entry{$fkey}
+           and $opts{$key} = $entry{$fkey};
+       }
+
+       my %errors;
+       my $im = bse_add_image($self->{cfg}, $product, %opts, 
+                              errors => \%errors);
+       $im 
+         or die join(", ",map "$_: $errors{$_}", keys %errors), "\n";
+       $callback
+         and $callback->(" $product->{id}: Add image '$file'");
+      }
       push @{$self->{products}}, $product;
     };
     if ($@) {
@@ -224,4 +272,15 @@ sub catalogs {
   return @{$_[0]{catalogs}};
 }
 
+sub _find_file {
+  my ($self, $file) = @_;
+
+  for my $path (@{$self->{file_path}}) {
+    my $full = "$path/$file";
+    -f $full and return $full;
+  }
+
+  return;
+}
+
 1;
index 1917181..2dffa2f 100644 (file)
@@ -13,8 +13,10 @@ chdir "$FindBin::Bin/../cgi-bin"
 
 my $verbose;
 my $delete;
+my @file_path;
 GetOptions("v", \$verbose,
-          "d", \$delete);
+          "d", \$delete,
+          "path|p=s", \@file_path);
 $verbose = defined $verbose;
 
 my $cfg = bse_cfg();
@@ -23,7 +25,11 @@ my $profile = shift;
 my $filename = shift
   or die "Usage: $0 profile filename\n";
 
-my $importer = BSE::ProductImportXLS->new($cfg, $profile);
+my $importer = BSE::ProductImportXLS->new
+  (
+   $cfg, $profile,
+   file_path => \@file_path
+  );
 
 my $callback;
 $verbose