Example #1
0
	/**
	 * Imports an actual product record in to the database.
	 *
	 * @param array Array of record data
	 */
	protected function _ImportRecord($record)
	{
		if(empty($record['prodname'])) {
			$this->ImportSession['Results']['Failures'][] = implode(",", $record['original_record'])." ".GetLang('ImportProductsMissingName');
			return;
		}

		if ($message = strtokenize($_REQUEST, '#')) {
			$this->ImportSession['Results']['Failures'][] = implode(",", $record['original_record'])." ".GetLang(B('UmVhY2hlZFByb2R1Y3RMaW1pdA=='));
			return;
		}

		$record = $this->normalizeInventoryTracking($record);

		$productHash = uniqid('IMPORT', true);
		$productId = 0;
		$hasThumb = false;
		$productFiles = array();
		$productImages = array();
		$existing = null;
		$isOverrideDuplicates = !empty($this->ImportSession['OverrideDuplicates']);
		$dupeCheckWhere = '';

		// Is there an existing product with this product ID ?
		if (!empty($record['productid'])) {
			$query = "SELECT * FROM [|PREFIX|]products WHERE productid = " . (int)$record['productid'];
			$result = $GLOBALS["ISC_CLASS_DB"]->Query($query);
			if($existing = $GLOBALS["ISC_CLASS_DB"]->Fetch($result)) {
				// Overriding existing products, set the product id
				if($isOverrideDuplicates) {
					$productId = $existing['productid'];
					$this->addImportResult('Updates', $record['prodname']);
				}
				else {
					// a product was found, but we're not updating existing record: skip
					$this->addImportResult('Duplicates', $record['prodname']);
					return;
				}

				// merge existing product details with the incoming record
				$record = $this->mergeExistingRecord($record, $existing);
			}
			else {
				// no product for this id was found, skip
				$this->addImportResult('Failures', $record['productid'] . " " . GetLang('ImportProductNotFound'));
				return;
			}

			$dupeCheckWhere  = " AND productid != " . (int)$record['productid'];
		}

		// Check if there is a different product with the same name
		$query = "SELECT * FROM [|PREFIX|]products WHERE prodname = '" . $GLOBALS['ISC_CLASS_DB']->Quote($record['prodname']) . "'" . $dupeCheckWhere;
		$result = $GLOBALS["ISC_CLASS_DB"]->Query($query);
		$differentProductWithSameName = $GLOBALS['ISC_CLASS_DB']->Fetch($result);

		if($differentProductWithSameName) {
			if($existing || !$isOverrideDuplicates) {
				$this->addImportResult('Duplicates', $record['prodname']);
				return;
			}

			$existing = $differentProductWithSameName;
			$productId = $existing['productid'];
			$this->addImportResult('Updates', $record['prodname']);

			$record = $this->mergeExistingRecord($record, $existing);
		}

		// Apply any default data
		$defaults = array(
			'prodprice' => 0,
			'prodcostprice' => 0,
			'prodretailprice' => 0,
			'prodsaleprice' => 0,
			'prodweight' => 0,
			'prodheight' => 0,
			'prodwidth' => 0,
			'proddepth' => 0,
			'prodsearchkeywords' => '',
			'prodsortorder' => 0,
			'prodvisible' => 1,
			'prodfeatured' => 0,
			'prodrelatedproducts' => '-1',
			'prodoptionsrequired' => 0,
			'prodfreeshipping' => 0,
			'prodlayoutfile' => '',
			'prodtags' => '',
			'prodcondition' => 'New',
			'prodshowcondition' => 0,
			'prodallowpurchases' => 1,
			'prodeventdaterequired' => 0,
			'prodeventdatefieldname' => '',
			'prodeventdatelimited' => 0,
			'prodeventdatelimitedtype' => 0,
			'prodeventdatelimitedstartdate' => 0,
			'prodeventdatelimitedenddate' => 0,
			'prodbrandid' => 0,
			'tax_class_name' => '',
			'upc' => '',
			'category' => null,
		);

		$record += $defaults;

		// check validity of price columns
		$priceFields = array(
			'prodprice',
			'prodcostprice',
			'prodsaleprice',
			'prodretailprice'
		);
		foreach ($priceFields as $field) {
			// price was invalid
			if (!IsPrice($record[$field])) {
				if ($productId) {
					// using existing price
					$record[$field] = $existing[$field];
				}
				else {
					$record[$field] = 0;
				}
				$this->addImportResult('Warnings', $record['prodname']." ".GetLang('ImportProductInvalidPrice'));
			}
		}

		// Do we have a product file?
		$productFiles = array();
		if (!$this->ImportSession['IsBulkEdit']) {
			if (!empty($record['prodfile'])) {
				$productFile = $this->_ImportFile($record);
				if ($productFile) {
					$productFiles[] = $productFile;
				}
			}
		}
		else {
			// bulk import files
			for ($x = 1; $x <= $this->ImportSession['MultiFieldCount']['files']; $x++) {
				if (empty($record['prodfile' . $x])) {
					continue;
				}

				$productFile = $this->_ImportFile($record, $x);
				if ($productFile) {
					$productFiles[] = $productFile;
				}
			}
		}


		// Do we have an image?
		$productImages = array();
		if (!$this->ImportSession['IsBulkEdit']) {
			if(!empty($record['prodimagefile'])) {
				$importedImage = $this->_ImportImage($productId, $record);
				if ($importedImage) {
					$productImages[] = $importedImage;
				}
			}
		}
		else {
			// bulk import images
			for ($x = 1; $x <= $this->ImportSession['MultiFieldCount']['images']; $x++) {
				if (empty($record['prodimagefile' . $x])) {
					if (empty($record['prodimageid' . $x])) {
						continue;
					}

					// image file is empty but an ID was supplied, we should delete the image
					if ($productId) {
						try {
							$image = new ISC_PRODUCT_IMAGE($record['prodimageid' . $x]);
							// ensure this image is associated with this product
							if ($image->getProductId() == $productId) {
								$image->delete();
							}
						}
						catch (Exception $ex) {
						}
					}

					continue;
				}

				$importedImage = $this->_ImportImage($productId, $record, $x);
				if ($importedImage) {
					$productImages[] = $importedImage;
				}
			}
		}

		// a category is not required if we have an existing record and ignore blanks is enabled
		$requireCatsField = !(!empty($record['productid']) && $this->ignoreBlankFields());
		$cats = $this->getImportRecordCategories($record);

		if($requireCatsField && empty($cats))
		{
			$this->addImportResult('Failures', implode(",", $record['original_record'])." ".GetLang('ImportProductsMissingCategory'));
			return;
		}

		// If there's a tax class, we need to fetch it now
		$record['tax_class_id'] = 0;
		if(!empty($record['tax_class_name'])) {
			static $taxClassCache = array();
			if(!isset($taxClassCache[$record['tax_class_name']])) {
				$query = "
					SELECT id
					FROM [|PREFIX|]tax_classes
					WHERE name='".$GLOBALS['ISC_CLASS_DB']->quote($record['tax_class_name'])."'
				";
				$taxClassCache[$record['tax_class_name']] = $GLOBALS['ISC_CLASS_DB']->fetchOne($query);
			}

			// Still don't have a matching tax class? Must be new.
			if(!$taxClassCache[$record['tax_class_name']]) {
				$newTaxClass = array(
					'name' => $record['tax_class_name']
				);
				$taxClassCache[$record['tax_class_name']] =
					$GLOBALS['ISC_CLASS_DB']->insertQuery('tax_classes', $newTaxClass);
			}

			$record['tax_class_id'] = $taxClassCache[$record['tax_class_name']];
		}

		// check the condition is valid
		$validConditions = array('new', 'used', 'refurbished');
		if (!isset($record['prodcondition']) || !in_array(isc_strtolower($record['prodcondition']), $validConditions)) {
			$record['prodcondition'] = 'New';
		}

		// Does the brand already exist?
		if(isset($record['brandname']) && $record['brandname'] != '') {
			$query = sprintf("select brandid from [|PREFIX|]brands where brandname='%s'", $GLOBALS['ISC_CLASS_DB']->Quote($record['brandname']));
			$result = $GLOBALS['ISC_CLASS_DB']->Query($query);
			if($row = $GLOBALS['ISC_CLASS_DB']->Fetch($result)) {
				$brandId = $row['brandid'];
			}
			// Create new brand
			else {
				// do we have permission to create brands?
				if ($GLOBALS["ISC_CLASS_ADMIN_AUTH"]->HasPermission(AUTH_Add_Brands)) {
					$newBrand = array(
						"brandname" => $record['brandname']
					);
					$brandId = $GLOBALS['ISC_CLASS_DB']->InsertQuery("brands", $newBrand);
				}
				else {
					// no brand creation permission, abort this record
					$this->addImportResult('Failures', $record['prodname'] . " " . GetLang('ImportNoPermissionCreateBrand'));
					return;
				}
			}

			$record['prodbrandid'] = $brandId;
		}
		else if(!$this->ignoreBlankFields()){
			$record['prodbrandid'] = 0;
		}

		if (isset($record['prodfile']) && $record['prodfile'] != '') {
			$productType = 2;
		} else if (isset($existing['prodtype']) && isId($existing['prodtype'])) {
			$productType = (int)$existing['prodtype'];
		} else {
			$productType = 1;
		}

		// event date
		$record['prodeventdaterequired'] = $this->StringToYesNoInt($record['prodeventdaterequired']);
		if ($record['prodeventdaterequired']) {
			// we must have an event name
			if (empty($record['prodeventdatefieldname'])) {
				$record['prodeventdaterequired'] = 0;
				$this->addImportResult('Warnings', $record['prodname'] . ' ' . GetLang('ImportNoEventDateName'));
			}
			else {
				$record['prodeventdatelimited'] = $this->StringToYesNoInt($record['prodeventdatelimited']);
				if ($record['prodeventdatelimited']) {
					if (!empty($record['prodeventdatelimitedstartdate'])) {
						$record['prodeventdatelimitedstartdate'] = (int)@ConvertDateToTime($record['prodeventdatelimitedstartdate']);
					}

					if (!empty($record['prodeventdatelimitedenddate'])) {
						$record['prodeventdatelimitedenddate'] = (int)@ConvertDateToTime($record['prodeventdatelimitedenddate']);
					}

					// determine what type of event date it is
					if ($record['prodeventdatelimitedstartdate'] > 0 && $record['prodeventdatelimitedenddate'] == 0) {
						$record['prodeventdatelimitedtype'] = 2; // start date
					}
					elseif ($record['prodeventdatelimitedstartdate'] == 0 && $record['prodeventdatelimitedenddate'] > 0) {
						$record['prodeventdatelimitedtype'] = 3; // end date
					}
					elseif ($record['prodeventdatelimitedenddate'] > $record['prodeventdatelimitedstartdate']) {
						$record['prodeventdatelimitedtype'] = 1; // date range
					}
					else {
						$record['prodeventdatelimited'] = 0;
						$this->addImportResults('Warnings', $record['prodname'] . ' ' . GetLang('ImportEventDateInvalid'));
					}
				}
			}
		}

		// Verify the inventory tracking method is valid.
		if($record['prodinvtrack'] == 2 && !($existing && $existing['prodvariationid'])) {
			$this->addImportResult('Warnings', $record['prodname'] . ' ' . GetLang('ImportProductTrackInventoryNoVariations'));
			$record['prodinvtrack'] = $existing['prodinvtrack'];
		}

		// This is our product
		$productData = array(
			"prodname" => $record['prodname'],
			"prodcode" => @$record['prodcode'],
			"proddesc" => @$record['proddesc'],
			"prodsearchkeywords" => @$record['prodsearchkeywords'],
			"prodtype" => $productType,
			"prodprice" => DefaultPriceFormat($record['prodprice']),
			"prodcostprice" => DefaultPriceFormat($record['prodcostprice']),
			"prodretailprice" => DefaultPriceFormat($record['prodretailprice']),
			"prodsaleprice" => DefaultPriceFormat($record['prodsaleprice']),
			"prodavailability" => @$record['prodavailability'],
			"prodsortorder" => $record['prodsortorder'],
			"prodvisible" => (int)$record['prodvisible'],
			"prodfeatured" => $record['prodfeatured'],
			"prodrelatedproducts" => $record['prodrelatedproducts'],
			"prodinvtrack" => (int)@$record['prodinvtrack'],
			"prodcurrentinv" => (int)@$record['prodcurrentinv'],
			"prodlowinv" => (int)@$record['prodlowinv'],
			"prodoptionsrequired" => $record['prodoptionsrequired'],
			"prodwarranty" => @$record['prodwarranty'],
			"prodheight" => DefaultDimensionFormat(@$record['prodheight']),
			"prodweight" => DefaultDimensionFormat(@$record['prodweight']),
			"prodwidth" => DefaultDimensionFormat(@$record['prodwidth']),
			"proddepth" => DefaultDimensionFormat(@$record['proddepth']),
			"prodfreeshipping" => (int)$record['prodfreeshipping'],
			"prodfixedshippingcost" => DefaultPriceFormat(@$record['prodfixedshippingcost']),
			"prodbrandid" => (int)$record['prodbrandid'],
			"prodcats" => $cats,
			"prodpagetitle" => @$record['prodpagetitle'],
			"prodmetakeywords" => @$record['prodmetakeywords'],
			"prodmetadesc" => @$record['prodmetadesc'],
			"prodlayoutfile" => $record['prodlayoutfile'],
			'prodtags' => $record['prodtags'],
			'prodmyobasset' => '',
			'prodmyobincome' => '',
			'prodmyobexpense' => '',
			'prodpeachtreegl' => '',
			'prodcondition' => $record['prodcondition'],
			'prodshowcondition' => (bool)$record['prodshowcondition'],
			'prodallowpurchases' => (bool)$record['prodallowpurchases'],
			'prodeventdaterequired' => $record['prodeventdaterequired'],
			'prodeventdatefieldname' => $record['prodeventdatefieldname'],
			'prodeventdatelimited' => $record['prodeventdatelimited'],
			'prodeventdatelimitedtype' => $record['prodeventdatelimitedtype'],
			'prodeventdatelimitedstartdate' => $record['prodeventdatelimitedstartdate'],
			'prodeventdatelimitedenddate' => $record['prodeventdatelimitedenddate'],
			'tax_class_id' => $record['tax_class_id'],
			'upc' => $record['upc'],
			'last_import' => $this->ImportSession['StartTime'],
		);

		/**
		 * The variation is part of the product record, so it will have to be attached to the record if this is an
		 * update AND the existing product already has a variation
		 */
		if (isset($existing) && is_array($existing) && isId($existing['prodvariationid'])) {
			$productData['prodvariationid'] = $existing['prodvariationid'];
		}

		$empty = array();

		// Save it
		$err = '';
		if (!$GLOBALS['ISC_CLASS_ADMIN_PRODUCT']->_CommitProduct($productId, $productData, $empty, $empty, $empty, $err, $empty, true)) {
			$this->addImportResult('Failures', $record['prodname'] . " " . GetLang('ImportDatabaseError'));
			return;
		}

		if($productId == 0) {
			$productId = $GLOBALS['NewProductId'];
		}

		// Post process images
		$existingImages = new ISC_PRODUCT_IMAGE_ITERATOR("SELECT * FROM `[|PREFIX|]product_images` WHERE imageprodid = " . (int)$productId);
		$maxSort = count($existingImages);
		if ($this->ImportSession['DeleteImages']) {
			foreach ($existingImages as $existingImage) {
				$existingImage->delete(false);
			}

			$maxSort = 0;
		}

		if(!empty($productImages)) {
			// sort the images
			usort($productImages, array($this, "_compare_images"));

			// update our images with the product id
			foreach ($productImages as $image) {
				$image->setProductId($productId);
				// ensure that an image doesn't have a sort set higher than max, or if no sort specified, then also set it to the highest.
				if ($image->getSort() > $maxSort || $image->getSort() === null) {
					$image->setSort($maxSort);
					$maxSort++;
				}
				$image->saveToDatabase(false);
			}
		}

		// Delete existing files
		if ($this->ImportSession['DeleteDownloads']) {
			$query = "
				SELECT
					*
				FROM
					[|PREFIX|]product_downloads
				WHERE
					productid = " . $productId;
			$result = $GLOBALS['ISC_CLASS_DB']->Query($query);
			while ($download = $GLOBALS['ISC_CLASS_DB']->Fetch($result)) {
				// Remove the file from the file system
				@unlink(GetConfig('DownloadDirectory') . "/" . $download['downfile']);

				// Delete from the database
				$GLOBALS['ISC_CLASS_DB']->DeleteQuery('product_downloads', 'WHERE downloadid = ' . $download['downloadid']);
			}
		}

		// Process product files
		if(!empty($productFiles)) {
			foreach($productFiles as $file) {
				$file['productid'] = $productId;
				$GLOBALS['ISC_CLASS_DB']->InsertQuery("product_downloads", $file);
			}
		}

		++$this->ImportSession['Results']['SuccessCount'];
	}
Example #2
0
	/**
	* Imports a temporary image file on the server to the given product. Performs validation, moves the file to it's final location and filename and returns an instance of ISC_PRODUCT_IMAGE.
	*
	* It is up to the method calling this to delete any temporary file if something goes wrong.
	*
	* @param string $temporaryPath Absolute path to the temporary image file stored on the server to be imported -- this file will need to be read so if it is an uploaded file and is in the tmp folder you should move it to the cache directory first since open_basedir restrictions may prevent the file being read from the tmp folder
	* @param string $originalFilename Original intended filename (such as the name provided by the browser when uploading a file) which may differ from the temporary file at $temporaryPath -- this should not include any directory components
	* @param int|string|bool $productId The id (or hash when $hash is true) of the product to import to, or supply as false to not save any info to the database but still return an instance of ISC_PRODUCT_IMAGE
	* @param bool $hash If true, $productId will be treated as a hash of a product in the process of being added
	* @param bool $moveTemporaryFile If true, the provided temporary file will be moved to it's new location, otherwise it will be copied
	* @param bool $generateImages If true, when importing, will attempt to generate thumbnail images -- may not be desirable if importing many images at once
	* @throws ISC_PRODUCT_IMAGE_IMPORT_INVALIDIMAGEFILE_EXCEPTION If the file is not a valid image
	* @throws ISC_PRODUCT_IMAGE_IMPORT_NOPHPSUPPORT_EXCEPTION If the image could not be processed by any installed php extensions
	* @throws ISC_PRODUCT_IMAGE_IMPORT_EMPTYIMAGE_EXCEPTION If the image is 'empty' - has 0 width or 0 height
	* @throws ISC_PRODUCT_IMAGE_IMPORT_CANTCREATEDIR_EXCEPTION If an error prevented the image's destination directory from being created (usually lack of write permissions on parent directory)
	* @throws ISC_PRODUCT_IMAGE_IMPORT_CANTMOVEFILE_EXCEPTION If an error prevented the image from being moved to the destination directory (usually lack of write permissions on parent directory)
	* @return ISC_PRODUCT_IMAGE If everything went OK
	*/
	public static function importImage($temporaryPath, $originalFilename, $productId, $hash = false, $moveTemporaryFile = true, $generateImages = true)
	{
		if (!file_exists($temporaryPath)) {
			throw new ISC_PRODUCT_IMAGE_SOURCEFILEDOESNTEXIST_EXCEPTION($temporaryPath);
		}

		try {
			$library = ISC_IMAGE_LIBRARY_FACTORY::getImageLibraryInstance($temporaryPath);
		} catch (ISC_IMAGE_LIBRARY_FACTORY_INVALIDIMAGEFILE_EXCEPTION $ex) {
			throw new ISC_PRODUCT_IMAGE_IMPORT_INVALIDIMAGEFILE_EXCEPTION();
		} catch (ISC_IMAGE_LIBRARY_FACTORY_NOPHPSUPPORT_EXCEPTION $ex) {
			throw new ISC_PRODUCT_IMAGE_IMPORT_NOPHPSUPPORT_EXCEPTION();
		}

		if ($library->getWidth() < 1 || $library->getHeight() < 1) {
			throw new ISC_PRODUCT_IMAGE_IMPORT_EMPTYIMAGE_EXCEPTION();
		}

		$finalName = $originalFilename;


		$finalName = basename($finalName); // remove any path components from the filename
		$finalName = self::sanitiseFilename($finalName);

		if (!self::isValidFilename($finalName, false)) {
			throw new ISC_PRODUCT_IMAGE_IMPORT_INVALIDFILENAME_EXCEPTION($finalName);
		}

		// correct the uploaded extension
		$correctExtension = $library->getImageTypeExtension(false);
		if (strtolower(pathinfo($finalName, PATHINFO_EXTENSION)) != $correctExtension) {
			// remove existing extension and trailing . if any
			$finalName = preg_replace('#\.[^\.]*$#', '', $finalName);
			// add correct extension
			$finalName .= '.' . $correctExtension;
		}

		// generate a path for storing in the product_images directory
		$finalRelativePath = self::generateSourceImageRelativeFilePath($finalName);

		$image = new ISC_PRODUCT_IMAGE();
		$image->setSourceFilePath($finalRelativePath);

		$finalAbsolutePath = $image->getAbsoluteSourceFilePath();
		$finalDirectory = dirname($finalAbsolutePath);

		if (!file_exists($finalDirectory)) {
			if (!isc_mkdir($finalDirectory, ISC_WRITEABLE_DIR_PERM, true)) {
				throw new ISC_PRODUCT_IMAGE_IMPORT_CANTCREATEDIR_EXCEPTION($finalDirectory);
			}
		}

		if ($moveTemporaryFile) {
			if (!@rename($temporaryPath, $finalAbsolutePath)) {
				throw new ISC_PRODUCT_IMAGE_IMPORT_CANTMOVEFILE_EXCEPTION($finalAbsolutePath);
			}
		} else {
			if (!@copy($temporaryPath, $finalAbsolutePath)) {
				throw new ISC_PRODUCT_IMAGE_IMPORT_CANTMOVEFILE_EXCEPTION($finalAbsolutePath);
			}
		}

		// check to see if the uploaded image exceeds our internal maximum image size: ISC_PRODUCT_IMAGE_MAXLONGEDGE
		if ($library->getWidth() > ISC_PRODUCT_IMAGE_MAXLONGEDGE || $library->getHeight() > ISC_PRODUCT_IMAGE_MAXLONGEDGE) {
			// if it is, resize it and overwrite the uploaded source image because we only want to store images to a maximum size of ISC_PRODUCT_IMAGE_MAXLONGEDGE x ISC_PRODUCT_IMAGE_MAXLONGEDGE
			$library->setFilePath($finalAbsolutePath);
			$library->loadImageFileToScratch();
			$library->resampleScratchToMaximumDimensions(ISC_PRODUCT_IMAGE_MAXLONGEDGE, ISC_PRODUCT_IMAGE_MAXLONGEDGE);
			$library->saveScratchToFile($finalAbsolutePath, self::getWriteOptionsForImageType($library->getImageType()));
		}

		if ($productId === false) {
			// do not assign product hash, id or save to database if $productId is false
			if ($generateImages) {
				// manually generate images since, normally, a call to saveToDatabase would do it
				$image->getResizedFileDimensions(ISC_PRODUCT_IMAGE_SIZE_TINY, true, false);
				$image->getResizedFileDimensions(ISC_PRODUCT_IMAGE_SIZE_THUMBNAIL, true, false);
				$image->getResizedFileDimensions(ISC_PRODUCT_IMAGE_SIZE_STANDARD, true, false);
				$image->getResizedFileDimensions(ISC_PRODUCT_IMAGE_SIZE_ZOOM, true, false);
			}

			return $image;
		}

		if ($hash) {
			$image->setProductHash($productId);
		} else {
			$image->setProductId($productId);
		}

		// ISC_PRODUCT_IMAGE_SOURCEFILEDOESNTEXIST_EXCEPTION should never really happen at this point with all the checks above so, if it does, let the exception go unhandled to bubble up to a fatal error
		$image->saveToDatabase($generateImages);

		return $image;
	}