diff --git a/magmi/plugins/base/itemprocessors/configurables/magmi_configurableprocessor.php b/magmi/plugins/base/itemprocessors/configurables/magmi_configurableprocessor.php index 0b826f55..3704d12e 100644 --- a/magmi/plugins/base/itemprocessors/configurables/magmi_configurableprocessor.php +++ b/magmi/plugins/base/itemprocessors/configurables/magmi_configurableprocessor.php @@ -6,9 +6,16 @@ class Magmi_ConfigurableItemProcessor extends Magmi_ItemProcessor private $_use_defaultopc = false; private $_optpriceinfo = array(); private $_currentsimples = array(); + private $baseImageCache = array(); + private $addsimpleimages; + private $backImageSupport; public function initialize($params) - {} + { + $this->addsimpleimages = $this->getParam("CFGR:addsimpleimages", 0); + $this->backImageSupport = $this->getParam("CFGR:backimage", 0); + + } /* Plugin info declaration */ public function getPluginInfo() { @@ -61,27 +68,41 @@ public function dolink($pid, $cond, $conddata = array()) $cpsl = $this->tablename("catalog_product_super_link"); $cpr = $this->tablename("catalog_product_relation"); $cpe = $this->tablename("catalog_product_entity"); + $sql = "DELETE cpsl.*,cpsr.* FROM $cpsl as cpsl JOIN $cpr as cpsr ON cpsr.parent_id=cpsl.parent_id WHERE cpsl.parent_id=?"; $this->delete($sql, array($pid)); - // recreate associations - $sql = "INSERT INTO $cpsl (`parent_id`,`product_id`) SELECT cpec.entity_id as parent_id,cpes.entity_id as product_id - FROM $cpe as cpec - JOIN $cpe as cpes ON cpes.type_id IN ('simple','virtual') AND cpes.sku $cond - WHERE cpec.entity_id=?"; - $this->insert($sql, array_merge($conddata, array($pid))); - $sql = "INSERT INTO $cpr (`parent_id`,`child_id`) SELECT cpec.entity_id as parent_id,cpes.entity_id as child_id + + //cache select results + $sql = "SELECT cpec.entity_id as parent_id,cpes.entity_id as product_id FROM $cpe as cpec JOIN $cpe as cpes ON cpes.type_id IN ('simple','virtual') AND cpes.sku $cond WHERE cpec.entity_id=?"; - $this->insert($sql, array_merge($conddata, array($pid))); + $rows = $this->selectAll($sql, array_merge($conddata, array($pid))); + + $ids = array(); + //convert result array into a string of values + foreach($rows as $row){ + $values .= "(".$row['parent_id'].",".$row['product_id']."),"; + $ids[] = $row['product_id']; + } + $values = rtrim($values, ','); + + if( ! empty($values) ){ + // recreate associations + $sql = "INSERT INTO $cpsl (`parent_id`,`product_id`) VALUES $values"; + $this->insert($sql); + $sql = "INSERT INTO $cpr (`parent_id`,`child_id`) VALUES $values"; + $this->insert($sql); + } unset($conddata); + return $ids; } public function autoLink($pid) { - $this->dolink($pid, "LIKE CONCAT(cpec.sku,'%')"); + return $this->dolink($pid, "LIKE CONCAT(cpec.sku,'%')"); } public function updSimpleVisibility($pid) @@ -101,7 +122,7 @@ public function updSimpleVisibility($pid) public function fixedLink($pid, $skulist) { - $this->dolink($pid, "IN (" . $this->arr2values($skulist) . ")", $skulist); + return $this->dolink($pid, "IN (" . $this->arr2values($skulist) . ")", $skulist); } public function buildSAPTable($sapdesc) @@ -311,24 +332,25 @@ public function processItemAfterId(&$item, $params = null) $idx++; } unset($confopts); + $ids = array(); switch ($matchmode) { case "none": break; case "auto": // destroy old associations - $this->autoLink($pid); + $ids = $this->autoLink($pid); $this->updSimpleVisibility($pid); break; case "cursimples": - $this->fixedLink($pid, $this->_currentsimples); + $ids = $this->fixedLink($pid, $this->_currentsimples); $this->updSimpleVisibility($pid); break; case "fixed": $sskus = explode(",", $item["simples_skus"]); trimarray($sskus); - $this->fixedLink($pid, $sskus); + $ids = $this->fixedLink($pid, $sskus); $this->updSimpleVisibility($pid); unset($item["simples_skus"]); unset($sskus); @@ -336,6 +358,12 @@ public function processItemAfterId(&$item, $params = null) default: break; } + + if($this->addsimpleimages >= 1){ + // Calling rewriteImageAttributes() here ensures that it runs before handleVarcharAttribute(). + $this->rewriteImageAttributes($item, $ids); + } + // always clear current simples if (count($this->_currentsimples) > 0) { @@ -344,7 +372,123 @@ public function processItemAfterId(&$item, $params = null) } return true; } + + /** + Cang Luo 31/10/2014 + Overwrite the image attributes with image paths from associated products only if it has associated products + This function must run before the handleVarcharAttribute() function of Image Attribute Processor + @param Array $item + : The item array. + @param Array $ids + : an array of IDs + */ + private function rewriteImageAttributes(&$item, $ids){ + $state=0; + if(count($ids) > 0){ + if($this->addsimpleimages>=2){ + $firstBaseImage = $this->fetchBaseImage($ids[0]); + if( !empty($firstBaseImage) ){ + $state += 1; + $item['image'] = $firstBaseImage; + $item['small_image'] = $item['image']; + $item['thumbnail'] = $item['image']; + } + } + $gallery = $this->fetchGalleryImages($item, $ids); + if( !empty($gallery) ){ + $state += 2; + $item['media_gallery'] = $gallery; + } + }else{ + $this->log("No associated products found for item ".$item['sku'].", fall back to original image values.", 'warning'); + } + $item['IMAGES_OVERWRITTEN']=$state; + } + /** + Cang Luo 03/11/2014 + select the base image path of given product ID from database + @param string $id + : an integer + @return string/false + : returns the path of base image of given product, + : returns false if $id is not in accepted format or no result found. + */ + public function fetchBaseImage($id){ + $id = trim($id); + if(!preg_match('/^\d+$/', $id)){ + $this->log("Invalid ID for base image: $id", 'warning'); + return false; + } + if(!isset($this->baseImageCache[$id])){ + $image_attinfo = $this->getAttrInfo('image'); + $attribute_id = $image_attinfo['attribute_id']; + $t = $this->tablename('catalog_product_entity_varchar'); + $sql = "SELECT value FROM $t WHERE attribute_id = $attribute_id AND entity_id = ?"; + $path = $this->selectone($sql, $id, 'value'); + if(is_null($path)){ + return false; + } + //caching image path + $this->baseImageCache[$id] = $path; + } + return $this->baseImageCache[$id]; + } + + /** + Cang Luo 03/11/2014 + Select the gallery image paths and labels of given product IDs from database + @param array $ids + : an array of integers + @return string/false + : returns a string of paths and labels, in the format of: path1[::label1]; path2[::label2];... + : returns false if $ids is not in accepted format. + */ + public function fetchGalleryImages($item, $ids){ + $idString = $ids; + if(is_array($ids)){ + $idString = implode(',' , $ids); + } + if(!preg_match('/^\d+(,\d+)*$/', $idString)){ + $this->log("Invalid IDs for gallery images: $idString", 'warning'); + return false; + } + $sids = $this->getItemStoreIds($item, 0); + $sids = '('.implode(",", $sids).')'; + $tg = $this->tablename('catalog_product_entity_media_gallery'); + $tgv = $this->tablename('catalog_product_entity_media_gallery_value'); + $sql = "SELECT value, label + FROM $tgv AS emgv + JOIN $tg AS emg ON emg.value_id = emgv.value_id + WHERE emg.entity_id in ($idString) AND emgv.store_id IN $sids AND value IS NOT NULL"; + $rows = $this->selectAll($sql); + + //back image support + if($this->backImageSupport == 1){ + $backImage = false; + if(count($ids) > 1){ + //get the base image of the second linked simple product + $backImage = $this->fetchBaseImage($ids[1]); + } + } + $ovalue = ''; + foreach($rows as $row){ + //back image support + if($this->backImageSupport == 1){ + if($row['label'] === 'back'){ + $row['label'] = ''; //reset existing 'back' label + } + if($row['value'] === $backImage){ + //set the label of base image of the second simple product to 'back' + $row['label'] = 'back'; + } + } + $ovalue .= $row['value']. (empty($row['label']) ? '' : '::'.$row['label']) .';'; + } + unset($rows); + return rtrim($ovalue, ';'); + } + public function processColumnList(&$cols, $params = null) { if (!in_array("options_container", $cols)) @@ -357,7 +501,7 @@ public function processColumnList(&$cols, $params = null) public function getPluginParamNames() { - return array("CFGR:simplesbeforeconf","CFGR:updsimplevis","CFGR:nolink"); + return array("CFGR:simplesbeforeconf","CFGR:updsimplevis","CFGR:nolink","CFGR:addsimpleimages","CFGR:backimage"); } static public function getCategory() diff --git a/magmi/plugins/base/itemprocessors/configurables/options_panel.php b/magmi/plugins/base/itemprocessors/configurables/options_panel.php index d68d0abf..9df71d9f 100755 --- a/magmi/plugins/base/itemprocessors/configurables/options_panel.php +++ b/magmi/plugins/base/itemprocessors/configurables/options_panel.php @@ -38,3 +38,26 @@ + + +
If 'All' is selected, it will assign the base image of the first associated product to be the base image, thumbnail and small image of the configurable product, and add all gallery images of associated products to the gallery of configurable product.
+ + +
Depends on the Auto Assign Images option. If yes, When auto adding images to the gallery of configurable, it will clear any existing 'back' label and set the label of the base image of the second associated product to 'back'. The labels of simple products will not be affected.
+ diff --git a/magmi/plugins/extra/itemprocessors/imageprocessor/imageitattributeemprocessor.php b/magmi/plugins/extra/itemprocessors/imageprocessor/imageitattributeemprocessor.php index 7ceebb0b..4be0ff67 100644 --- a/magmi/plugins/extra/itemprocessors/imageprocessor/imageitattributeemprocessor.php +++ b/magmi/plugins/extra/itemprocessors/imageprocessor/imageitattributeemprocessor.php @@ -7,13 +7,13 @@ class ImageAttributeItemProcessor extends Magmi_ItemProcessor protected $imgsourcedirs = array(); protected $errattrs = array(); protected $_errorimgs = array(); - protected $_lastimage = ""; protected $_handled_attributes = array(); protected $_img_baseattrs = array("image","small_image","thumbnail"); protected $_active = false; protected $_newitem; protected $_mdh; protected $_remoteroot = ""; + protected $wildcard; protected $debug; public function initialize($params) @@ -41,6 +41,7 @@ public function initialize($params) } } $this->debug = $this->getParam("IMG:debug", 0); + $this->wildcard = $this->getParam("IMG:wildcard", 0); } public function getPluginInfo() @@ -93,44 +94,49 @@ public function handleRemoveImages($pid, &$item, $ivalue) public function handleGalleryTypeAttribute($pid, &$item, $storeid, $attrcode, $attrdesc, $ivalue) { // do nothing if empty - if ($ivalue == "") - { - return false; - } - // use ";" as image separator - $images = explode(";", $ivalue); - $imageindex = 0; - // for each image - foreach ($images as $imagefile) - { - // trim image file in case of spaced split - $imagefile = trim($imagefile); - // handle exclude flag explicitely - $exclude = $this->getExclude($imagefile, false); - $infolist = explode("::", $imagefile); - $label = null; - if (count($infolist) > 1) - { - $label = $infolist[1]; - $imagefile = $infolist[0]; - } - unset($infolist); - $extra=array("store"=>$storeid,"attr_code"=>$attrcode,"imageindex"=>$imageindex == 0 ? "" : $imageindex); - // copy it from source dir to product media dir - $imagefile = $this->copyImageFile($imagefile, $item, $extra); + if ($ivalue == ""){ return false; } + + $targetsids = $this->getStoreIdsForStoreScope($item["store"]); + + // use ";" as image separator + $images = explode(";", $ivalue); + $imageindex = 0; + // for each image + foreach ($images as $imagefile) + { + // trim image file in case of spaced split + $imagefile = trim($imagefile); + // handle exclude flag explicitely + $exclude = $this->getExclude($imagefile, false); + $infolist = explode("::", $imagefile); + $label = null; + if (count($infolist) > 1) + { + $label = $infolist[1]; + $imagefile = $infolist[0]; + } + unset($infolist); + $extra=array("store"=>$storeid,"attr_code"=>$attrcode,"imageindex"=>$imageindex == 0 ? "" : $imageindex); + + //if gallery attributes have been overwritten by configurable processor, then no need to copy. + if(isset($item['IMAGES_OVERWRITTEN']) && $item['IMAGES_OVERWRITTEN'] >=2 ){ + $imagefiles = array($imagefile); + }else{ + // copy it from source dir to product media dir + $imagefiles = $this->copyImageFile($imagefile, $item, $extra); + } unset($extra); - if ($imagefile !== false) - { - // add to gallery - $targetsids = $this->getStoreIdsForStoreScope($item["store"]); - $vid = $this->addImageToGallery($pid, $storeid, $attrdesc, $imagefile, $targetsids, $label, $exclude); - } - $imageindex++; - } - unset($images); + + // add to gallery + foreach($imagefiles as $file){ + $vid = $this->addImageToGallery($pid, $storeid, $attrdesc, $file, $targetsids, $label, $exclude); + $imageindex++; + } + } + unset($images); + // we don't want to insert after that - $ovalue = false; - return $ovalue; + return false; } public function removeImageFromGallery($pid, $storeid, $attrdesc) @@ -155,6 +161,12 @@ public function getExclude(&$val, $default = true) return $exclude; } + /** Cang Luo - 07/10/2014 + * If the relative image path is a wildcard path, find and return all match image (absolute) paths as array + * If it is not a wildcard path, find and return the absolute path as an array (of 1 element) + * @param $ivalue : relative image path, allows wildcard (*) + * @return : an array of absolute image paths on success, false if not found + */ public function findImageFile($ivalue) { // do no try to find remote image @@ -168,6 +180,24 @@ public function findImageFile($ivalue) return $ivalue; } + //Cang Luo - 7/10/2014 Check if ivalue is a valid wildcard path + $isWild = false; + $path = ''; + $name = $ivalue; + //if the image path is a valid wildcard path, split the path at the last / + if ($this->wildcard==1 && preg_match("/.*\*\w*\.(jpg|jpeg|png|gif)$/", $ivalue)){ + //get the index of the last / + $isWild = true; + $pos = strrpos ($ivalue, '/', -0); + if($pos !== false){ + $path = substr($ivalue, 0, $pos+1); + $name = substr($ivalue, $pos+1); + } + //replace * with .* (turn name into regular expression) + $name = str_replace('.', '\.', $name); + $name = str_replace('*', '.*', $name); + } + // ok , so it's a relative path $imgfile = false; $scandirs = explode(";", $this->getParam("IMG:sourcedir")); @@ -181,9 +211,36 @@ public function findImageFile($ivalue) { $sd = $this->_mdh->getMagentoDir() . "/" . $sd; } - $imgfile = abspath($ivalue, $sd, true); - } - return $imgfile; + + //Cang Luo - 7/10/2014 if it is a wildcard path, + if($isWild){ + $sd .= $path; + $sd = realpath($sd); + + if($sd){ + $imgfile = array(); + + // scan directory for files that matched the regex + if ($handle = opendir($sd)) { + while (false !== ($entry = readdir($handle))) { + if (preg_match("/^$name$/i", $entry)) { + $imgfile[] = abspath($entry,$sd); + } + } + closedir($handle); + } + if(empty($imgfile)){ + //if no match, set imgfile back to false, to scan the next source directory; + $imgfile = false; + } + } + }else{ //ordinary path + if($imgfile=abspath($ivalue,$sd)){ + $imgfile=array($imgfile); + } + } + } + return $imgfile; //returns array or false } public function handleImageTypeAttribute($pid, &$item, $storeid, $attrcode, $attrdesc, $ivalue) @@ -195,28 +252,32 @@ public function handleImageTypeAttribute($pid, &$item, $storeid, $attrcode, $att return "__MAGMI_DELETE__"; } - // add support for explicit exclude - $exclude = $this->getExclude($ivalue, true); + // add support for explicit exclude, default to false (include image) + $exclude = $this->getExclude($ivalue, false); $imagefile = trim($ivalue); - // else copy image file - $imagefile = $this->copyImageFile($imagefile, $item, array("store"=>$storeid,"attr_code"=>$attrcode)); - $ovalue = $imagefile; + //if image attributes have been overwritten by configurable processor, then no need to copy. + if(isset($item['IMAGES_OVERWRITTEN']) && ( $item['IMAGES_OVERWRITTEN']==1 or $item['IMAGES_OVERWRITTEN']==3 ) ){ + $imagefiles = array($imagefile); + }else{ + $imagefiles = $this->copyImageFile($imagefile, $item, array("store"=>$storeid,"attr_code"=>$attrcode)); + } + $ovalue = false; // add to gallery as excluded - if ($imagefile !== false) - { + if(is_array($imagefiles) && count($imagefiles) > 0 && $imagefiles[0] !== false){ $label = null; if (isset($item[$attrcode . "_label"])) { $label = $item[$attrcode . "_label"]; } $targetsids = $this->getStoreIdsForStoreScope($item["store"]); - $vid = $this->addImageToGallery($pid, $storeid, $attrdesc, $imagefile, $targetsids, $label, $exclude, - $attrdesc["attribute_id"]); + $ovalue=$imagefiles[0]; + $vid = $this->addImageToGallery($pid, $storeid, $attrdesc, $ovalue, $targetsids, $label, $exclude, $attrdesc["attribute_id"]); } return $ovalue; } + public function handleVarcharAttribute($pid, &$item, $storeid, $attrcode, $attrdesc, $ivalue) { if (trim($ivalue) == "") @@ -362,6 +423,8 @@ public function addImageToGallery($pid, $storeid, $attrdesc, $imgname, $targetsi if ($imglabel != null) { $data[] = $imglabel; + //if image label is not specified, do not update the label to null. + $updatelabel = "label=VALUES(`label`),"; } } @@ -370,7 +433,7 @@ public function addImageToGallery($pid, $storeid, $attrdesc, $imgname, $targetsi $sql = "INSERT INTO $tgv (value_id,store_id,position,disabled,label) VALUES " . implode(",", $vinserts) . " - ON DUPLICATE KEY UPDATE label=VALUES(`label`),disabled=VALUES(`disabled`)"; + ON DUPLICATE KEY UPDATE $updatelabel disabled=VALUES(`disabled`)"; $this->insert($sql, $data); } unset($vinserts); @@ -410,10 +473,15 @@ public function getImagenameComponents($fname, $formula, $extra) { $matches = array(); $xname = $fname; + if (preg_match("|re::(.*)::(.*)|", $formula, $matches)) { $rep = $matches[2]; - $xname = preg_replace("|" . $matches[1] . "|", $rep, $xname); + $pattern = $matches[1]; + if ('\\' === DIRECTORY_SEPARATOR) + //fix image renaming bug on windows + $pattern = str_replace('/', '\\\\', $pattern); + $xname = preg_replace("|$pattern|", $rep, $xname); $extra['parsed'] = true; } $xname = basename($xname); @@ -443,7 +511,6 @@ public function getTargetName($fname, $item, $extra) $cname = $this->parsename($pname, $item, $extra); } $cname = strtolower(preg_replace("/%[0-9][0-9|A-F]/", "_", rawurlencode($cname))); - return $cname; } @@ -454,13 +521,12 @@ public function saveImage($imgfile, $target) } /** - * copy image file from source directory to - * product media directory + * copy image file(s) from source directory to product media directory * * @param $imgfile : - * name of image file name in source directory - * @return : name of image file name relative to magento catalog media dir,including leading - * directories made of first char & second char of image file name. + * relative image file path (may contain wildcard) in source directory + * @return : an array of image file names relative to magento catalog media dir, including leading + * directories made of first char & second char of image file name; */ public function copyImageFile($imgfile, &$item, $extra) { @@ -480,74 +546,72 @@ public function copyImageFile($imgfile, &$item, $extra) return false; } - $source = $this->findImageFile($imgfile); - if ($source == false) + $files = $this->findImageFile($imgfile); + if ($files === false) { $this->log("$imgfile cannot be found in images path", "warning"); // last image in error,add it to error cache $this->setErrorImg($imgfile); return false; } - $imgfile = $source; - $checkexist = ($this->getParam("IMG:existingonly") == "yes"); - $curlh = false; - $bimgfile = $this->getTargetName($imgfile, $item, $extra); - // source file exists - $i1 = $bimgfile[0]; - $i2 = $bimgfile[1]; - // magento image value (relative to media catalog) - $impath = "/$i1/$i2/$bimgfile"; - // target directory; - $l2d = "media/catalog/product/$i1/$i2"; - // test for existence - $targetpath = "$l2d/$bimgfile"; - /* test for same image (without problem) */ - if ($impath == $this->_lastimage) - { - return $impath; - } - /* test if imagefile comes from export */ - if (!$this->_mdh->file_exists($targetpath) || $this->getParam("IMG:writemode") == "override") - { - // if we already had problems with this target,assume we'll get others. - if ($this->isErrorImage($impath)) - { - return false; - } - - /* try to recursively create target dir */ - if (!$this->_mdh->file_exists($l2d)) - { - - $tst = $this->_mdh->mkdir($l2d, Magmi_Config::getInstance()->getDirMask(), true); - if (!$tst) - { - // if we had problem creating target directory,add target to error cache - $errors = $this->_mdh->getLastError(); - $this->log("error creating $l2d: {$errors["type"]},{$errors["message"]}", "warning"); - unset($errors); - $this->setErrorImg($impath); - return false; - } - } - - if (!$this->saveImage($imgfile, $targetpath)) - { - $errors = $this->_mdh->getLastError(); - $this->fillErrorAttributes($item); - $this->log("error copying $l2d/$bimgfile : {$errors["type"]},{$errors["message"]}", "warning"); - unset($errors); - $this->setErrorImg($impath); - return false; - } - else - { - @$this->_mdh->chmod("$l2d/$bimgfile", Magmi_Config::getInstance()->getFileMask()); - } - } - $this->_lastimage = $impath; - /* return image file name relative to media dir (with leading / ) */ - return $impath; + + $media_paths = array(); + foreach ($files as $file){ + $bimgfile=$this->getTargetName($file,$item,$extra); + //source file exists + $i1=$bimgfile[0]; + $i2=$bimgfile[1]; + + // magento image value (relative to media catalog) + $path = "/$i1/$i2/$bimgfile"; + + // target directory; + $l2d="media/catalog/product/$i1/$i2"; + $targetpath="$l2d/$bimgfile"; + + + /* test if imagefile comes from export */ + if(!$this->_mdh->file_exists("$targetpath") || $this->getParam("IMG:writemode")=="override") + { + // if we already had problems with this target,assume we'll get others. + if (!$this->isErrorImage($path)) + { + $_continue = true; + /* try to recursively create target dir */ + if(!$this->_mdh->file_exists("$l2d")) + { + $tst=$this->_mdh->mkdir($l2d,Magmi_Config::getInstance()->getDirMask(),true); + if(!$tst) + { + $errors=$this->_mdh->getLastError(); + $this->log("error creating $l2d: {$errors["type"]},{$errors["message"]}","warning"); + unset($errors); + $this->setErrorImg($path); + $_continue = false; + } + } + + if($_continue && !$this->saveImage($file,"$l2d/$bimgfile")) + { + $errors=$this->_mdh->getLastError(); + $this->fillErrorAttributes($item); + $this->log("error copying $file TO $l2d/$bimgfile : {$errors["type"]},{$errors["message"]}","warning"); + unset($errors); + $this->setErrorImg($path); + $_continue = false; + } + else + { + $this->_mdh->chmod("$l2d/$bimgfile",Magmi_Config::getInstance()->getFileMask()); + $media_paths[]=$path; + } + } + }else{ + $media_paths[]=$path; + } + } + /* return an array of image file names relative to media dir (with leading / ) */ + return $media_paths; } public function updateLabel($attrdesc, $pid, $sids, $label) diff --git a/magmi/plugins/extra/itemprocessors/imageprocessor/options_panel.php b/magmi/plugins/extra/itemprocessors/imageprocessor/options_panel.php index a17d58ca..5e250d91 100755 --- a/magmi/plugins/extra/itemprocessors/imageprocessor/options_panel.php +++ b/magmi/plugins/extra/itemprocessors/imageprocessor/options_panel.php @@ -65,6 +65,16 @@ + +
Example: /cat1/sku01/*.jpg will match all jpg files in /cat1/sku01/ folder
+
Debug mode