diff --git a/core/lib/Thelia/Config/Resources/export.xml b/core/lib/Thelia/Config/Resources/export.xml
index 29040f34f..b212b8dbc 100644
--- a/core/lib/Thelia/Config/Resources/export.xml
+++ b/core/lib/Thelia/Config/Resources/export.xml
@@ -27,6 +27,17 @@
+
+
+ Clients
+ Exporter toutes les informations à propos de vos clients
+
+
+ Customers
+ Export all the information about your customers
+
+
+
Prix des produits Hors-Taxes
diff --git a/core/lib/Thelia/ImportExport/AbstractHandler.php b/core/lib/Thelia/ImportExport/AbstractHandler.php
index 6857fff88..e1ecaca95 100644
--- a/core/lib/Thelia/ImportExport/AbstractHandler.php
+++ b/core/lib/Thelia/ImportExport/AbstractHandler.php
@@ -12,6 +12,7 @@
namespace Thelia\ImportExport;
use Symfony\Component\DependencyInjection\ContainerInterface;
+use Thelia\Model\Lang;
/**
* Class AbstractHandler
@@ -23,12 +24,15 @@ abstract class AbstractHandler
/** @var \Symfony\Component\DependencyInjection\ContainerInterface */
protected $container;
+ protected $defaultLocale;
/**
* @param ContainerInterface $container
*
* Dependency injection: load the container to be able to get parameters and services
*/
public function __construct(ContainerInterface $container) {
+ $this->defaultLocale = Lang::getDefaultLanguage()->getLocale();
+
$this->container = $container;
}
diff --git a/core/lib/Thelia/ImportExport/Export/ExportHandler.php b/core/lib/Thelia/ImportExport/Export/ExportHandler.php
index 50360953c..d14fa6bca 100644
--- a/core/lib/Thelia/ImportExport/Export/ExportHandler.php
+++ b/core/lib/Thelia/ImportExport/Export/ExportHandler.php
@@ -14,6 +14,7 @@ namespace Thelia\ImportExport\Export;
use Propel\Runtime\ActiveQuery\Criterion\Exception\InvalidValueException;
use Propel\Runtime\ActiveQuery\ModelCriteria;
use Thelia\Core\FileFormat\Formatting\FormatterData;
+use Thelia\Core\Template\Element\BaseLoop;
use Thelia\Core\Translation\Translator;
use Thelia\Model\Lang;
use Thelia\ImportExport\AbstractHandler;
@@ -30,60 +31,6 @@ abstract class ExportHandler extends AbstractHandler
/** @var array */
protected $order;
- public function addI18nCondition(
- ModelCriteria $query,
- $i18nTableName,
- $tableIdColumn,
- $i18nIdColumn,
- $localeColumn,
- $locale
- ) {
-
- $locale = $this->real_escape($locale);
- $defaultLocale = $this->real_escape(Lang::getDefaultLanguage()->getLocale());
-
- $query
- ->_and()
- ->where(
- "CASE WHEN ".$tableIdColumn." IN".
- "(SELECT DISTINCT ".$i18nIdColumn." ".
- "FROM `".$i18nTableName."` ".
- "WHERE locale=$locale) ".
-
- "THEN ".$localeColumn." = $locale ".
- "ELSE ".$localeColumn." = $defaultLocale ".
- "END"
- )
- ;
- }
-
- /**
- * @param $str
- * @return string
- *
- * Really escapes a string for SQL request.
- */
- protected function real_escape($str)
- {
- $str = trim($str, "\"'");
-
- $return = "CONCAT(";
- $len = strlen($str);
-
- for($i = 0; $i < $len; ++$i) {
- $return .= "CHAR(".ord($str[$i])."),";
- }
-
- if ($i > 0) {
- $return = substr($return, 0, -1);
- } else {
- $return = "\"\"";
- }
- $return .= ")";
-
- return $return;
- }
-
/**
* @return array
*
@@ -96,6 +43,17 @@ abstract class ExportHandler extends AbstractHandler
return array();
}
+ /**
+ * @return null|array
+ *
+ * You may override this method to return an array, containing
+ * the aliases to use.
+ */
+ protected function getAliases()
+ {
+ return null;
+ }
+
/**
* @return array
*
@@ -133,11 +91,24 @@ abstract class ExportHandler extends AbstractHandler
$query = $this->buildDataSet($lang);
if ($query instanceof ModelCriteria) {
+
return $data->loadModelCriteria($query);
} elseif (is_array($query)) {
+
return $data->setData($query);
+ } elseif ($query instanceof BaseLoop) {
+ $pagination = null;
+ $results = $query->exec($pagination);
+
+ for ($results->rewind(); $results->valid(); $results->next() ) {
+ $current = $results->current();
+
+ $data->addRow($current->getVarVal());
+ }
+
+ return $data;
}
-
+
throw new InvalidValueException(
Translator::getInstance()->trans(
"The method \"%class\"::buildDataSet must return an array or a ModelCriteria",
@@ -148,18 +119,63 @@ abstract class ExportHandler extends AbstractHandler
);
}
+ public function addI18nCondition(
+ ModelCriteria $query,
+ $i18nTableName,
+ $tableIdColumn,
+ $i18nIdColumn,
+ $localeColumn,
+ $locale
+ ) {
+
+ $locale = $this->real_escape($locale);
+ $defaultLocale = $this->real_escape($this->defaultLocale);
+
+ $query
+ ->_and()
+ ->where(
+ "CASE WHEN ".$tableIdColumn." IN".
+ "(SELECT DISTINCT ".$i18nIdColumn." ".
+ "FROM `".$i18nTableName."` ".
+ "WHERE locale=$locale) ".
+
+ "THEN ".$localeColumn." = $locale ".
+ "ELSE ".$localeColumn." = $defaultLocale ".
+ "END"
+ )
+ ;
+ }
+
/**
- * @return null|array
+ * @param $str
+ * @return string
*
+ * Really escapes a string for SQL request.
*/
- protected function getAliases()
+ protected function real_escape($str)
{
- return null;
+ $str = trim($str, "\"'");
+
+ $return = "CONCAT(";
+ $len = strlen($str);
+
+ for($i = 0; $i < $len; ++$i) {
+ $return .= "CHAR(".ord($str[$i])."),";
+ }
+
+ if ($i > 0) {
+ $return = substr($return, 0, -1);
+ } else {
+ $return = "\"\"";
+ }
+ $return .= ")";
+
+ return $return;
}
/**
* @param Lang $lang
- * @return ModelCriteria|array
+ * @return ModelCriteria|array|BaseLoop
*/
abstract protected function buildDataSet(Lang $lang);
}
\ No newline at end of file
diff --git a/core/lib/Thelia/ImportExport/Export/Type/CustomerExport.php b/core/lib/Thelia/ImportExport/Export/Type/CustomerExport.php
new file mode 100644
index 000000000..bbfa5bf8b
--- /dev/null
+++ b/core/lib/Thelia/ImportExport/Export/Type/CustomerExport.php
@@ -0,0 +1,360 @@
+
+ */
+class CustomerExport extends ExportHandler
+{
+ /**
+ * @return string|array
+ *
+ * Define all the type of formatters that this can handle
+ * return a string if it handle a single type ( specific exports ),
+ * or an array if multiple.
+ *
+ * Thelia types are defined in \Thelia\Core\FileFormat\FormatType
+ *
+ * example:
+ * return array(
+ * FormatType::TABLE,
+ * FormatType::UNBOUNDED,
+ * );
+ */
+ public function getHandledTypes()
+ {
+ return array(
+ FormatType::TABLE,
+ FormatType::UNBOUNDED,
+ );
+ }
+
+ /**
+ * @param Lang $lang
+ * @return array|\Propel\Runtime\ActiveQuery\ModelCriteria
+ *
+ * The tax engine of Thelia is in PHP, so we can't compute orders for each customers
+ * directly in SQL, we need two SQL queries, and some computing to get the last order amount and total amount.
+ */
+ public function buildDataSet(Lang $lang)
+ {
+ $locale = $lang->getLocale();
+ $defaultLocale = Lang::getDefaultLanguage()->getLocale();
+ /**
+ * This first query get each customer info and addresses.
+ */
+ $newsletterJoin = new Join(CustomerTableMap::EMAIL, NewsletterTableMap::EMAIL, Criteria::LEFT_JOIN);
+
+ $query = CustomerQuery::create()
+ ->useCustomerTitleQuery("customer_title_")
+ ->useCustomerTitleI18nQuery("customer_title_i18n_")
+ ->addAsColumn("title_TITLE", "customer_title_i18n_.SHORT")
+ ->endUse()
+ ->endUse()
+ ->useAddressQuery()
+ ->useCountryQuery()
+ ->useCountryI18nQuery()
+ ->addAsColumn("address_COUNTRY", CountryI18nTableMap::TITLE)
+ ->endUse()
+ ->endUse()
+ ->useCustomerTitleQuery("address_title")
+ ->useCustomerTitleI18nQuery("address_title_i18n")
+ ->addAsColumn("address_TITLE", "address_title_i18n.SHORT")
+ ->endUse()
+ ->endUse()
+ ->addAsColumn("address_LABEL", AddressTableMap::LABEL)
+ ->addAsColumn("address_FIRST_NAME", AddressTableMap::FIRSTNAME)
+ ->addAsColumn("address_LAST_NAME", AddressTableMap::LASTNAME)
+ ->addAsColumn("address_COMPANY", AddressTableMap::COMPANY)
+ ->addAsColumn("address_ADDRESS1", AddressTableMap::ADDRESS1)
+ ->addAsColumn("address_ADDRESS2", AddressTableMap::ADDRESS2)
+ ->addAsColumn("address_ADDRESS3", AddressTableMap::ADDRESS3)
+ ->addAsColumn("address_ZIPCODE", AddressTableMap::ZIPCODE)
+ ->addAsColumn("address_CITY", AddressTableMap::CITY)
+ ->addAsColumn("address_PHONE", AddressTableMap::PHONE)
+ ->addAsColumn("address_CELLPHONE", AddressTableMap::CELLPHONE)
+ ->addAsColumn("address_IS_DEFAULT", AddressTableMap::IS_DEFAULT)
+ ->endUse()
+ ->addJoinObject($newsletterJoin)
+ ->addAsColumn("newsletter_IS_REGISTRED", "IF(NOT ISNULL(".NewsletterTableMap::EMAIL."),1,0)")
+ ->select([
+ CustomerTableMap::ID,
+ CustomerTableMap::REF,
+ CustomerTableMap::LASTNAME,
+ CustomerTableMap::FIRSTNAME,
+ CustomerTableMap::EMAIL,
+ CustomerTableMap::DISCOUNT,
+ CustomerTableMap::CREATED_AT,
+ "title_TITLE",
+ "address_TITLE",
+ "address_LABEL",
+ "address_COMPANY",
+ "address_FIRST_NAME",
+ "address_LAST_NAME",
+ "address_ADDRESS1",
+ "address_ADDRESS2",
+ "address_ADDRESS3",
+ "address_ZIPCODE",
+ "address_CITY",
+ "address_COUNTRY",
+ "address_PHONE",
+ "address_CELLPHONE",
+ "address_IS_DEFAULT",
+ "newsletter_IS_REGISTRED",
+ ])
+ ->orderById()
+ ;
+
+ $this->addI18nCondition(
+ $query,
+ CountryI18nTableMap::TABLE_NAME,
+ CountryTableMap::ID,
+ CountryI18nTableMap::ID,
+ CountryI18nTableMap::LOCALE,
+ $locale
+ );
+
+ $this->addI18nCondition(
+ $query,
+ CustomerTitleI18nTableMap::TABLE_NAME,
+ "`customer_title_`.ID",
+ "`customer_title_i18n_`.ID",
+ "`customer_title_i18n_`.LOCALE",
+ $locale
+ );
+
+ $this->addI18nCondition(
+ $query,
+ CustomerTitleI18nTableMap::TABLE_NAME,
+ "`address_title`.ID",
+ "`address_title_i18n`.ID",
+ "`address_title_i18n`.LOCALE",
+ $locale
+ );
+
+ /** @var CustomerQuery $query */
+ $results = $query
+ ->find()
+ ->toArray()
+ ;
+
+ /**
+ * Then get the orders
+ */
+ $orders = OrderQuery::create()
+ ->useCustomerQuery()
+ ->orderById()
+ ->endUse()
+ ->find()
+ ;
+
+ /**
+ * And add them info the array
+ */
+ $orders->rewind();
+
+ $arrayLength = count($results);
+
+ $previousCustomerId = null;
+
+ for ($i = 0; $i < $arrayLength; ++$i) {
+ $currentCustomer = &$results[$i];
+
+ $currentCustomerId = $currentCustomer[CustomerTableMap::ID];
+ unset ($currentCustomer[CustomerTableMap::ID]);
+
+ if ($currentCustomerId === $previousCustomerId) {
+ $currentCustomer["title_TITLE"] = "";
+ $currentCustomer[CustomerTableMap::LASTNAME] = "";
+ $currentCustomer[CustomerTableMap::FIRSTNAME] = "";
+ $currentCustomer[CustomerTableMap::EMAIL] = "";
+ $currentCustomer["address_COMPANY"] = "";
+ $currentCustomer["newsletter_IS_REGISTRED"] = "";
+ $currentCustomer[CustomerTableMap::CREATED_AT] = "";
+ $currentCustomer[CustomerTableMap::DISCOUNT] = "";
+
+ $currentCustomer += [
+ "order_TOTAL" => "",
+ "last_order_AMOUNT" => "",
+ "last_order_DATE" => "",
+ ];
+ } else {
+
+ /**
+ * Reformat created_at date
+ */
+ $date = $currentCustomer[CustomerTableMap::CREATED_AT];
+ $dateTime = new \DateTime($date);
+ $currentCustomer[CustomerTableMap::CREATED_AT] = $dateTime->format($lang->getDatetimeFormat());
+
+
+ /**
+ * Then compute everything about the orders
+ */
+ $total = 0;
+ $lastOrderAmount = 0;
+ $lastOrderDate = null;
+ $lastOrder = null;
+ $lastOrderCurrencyCode = null;
+
+ $defaultCurrency = Currency::getDefaultCurrency();
+ $defaultCurrencyCode = $defaultCurrency
+ ->setLocale($locale)
+ ->getCode()
+ ;
+
+ if (empty($defaultCurrencyCode)) {
+ $defaultCurrencyCode = $defaultCurrency
+ ->setLocale($defaultLocale)
+ ->getCode()
+ ;
+ }
+
+ $formattedDate = null;
+
+ /** @var \Thelia\Model\Order $currentOrder */
+ while (false !== $currentOrder = $orders->current()) {
+ if ($currentCustomerId != $currentOrder->getCustomerId()) {
+ break;
+ }
+
+ $amount = $currentOrder->getTotalAmount($tax);
+ if (0 < $rate = $currentOrder->getCurrencyRate()) {
+ $amount = round($amount / $rate, 2);
+ }
+
+ $total += $amount;
+
+ /** @var \DateTime $date */
+ $date = $currentOrder->getCreatedAt();
+
+ if (null === $lastOrderDate || $date > $lastOrderDate) {
+ $lastOrder = $currentOrder;
+ $lastOrderDate = $date;
+ }
+
+ $orders->next();
+ }
+
+ if ($lastOrderDate !== null) {
+ $formattedDate = $lastOrderDate->format($lang->getDatetimeFormat());
+
+ $orderCurrency = $lastOrder->getCurrency();
+ $lastOrderCurrencyCode = $orderCurrency
+ ->setLocale($locale)
+ ->getCode()
+ ;
+
+ if (empty($lastOrderCurrencyCode)) {
+ $lastOrderCurrencyCode = $orderCurrency
+ ->setLocale($defaultLocale)
+ ->getCode()
+ ;
+ }
+
+ $lastOrderAmount = $lastOrder->getTotalAmount($tax_);
+ }
+
+ $currentCustomer += [
+ "order_TOTAL" => $total . " " . $defaultCurrencyCode,
+ "last_order_AMOUNT" => $lastOrderAmount === 0 ? "" : $lastOrderAmount . " " . $lastOrderCurrencyCode,
+ "last_order_DATE" => $formattedDate,
+ ];
+ }
+
+ $previousCustomerId = $currentCustomerId;
+ }
+
+ return $results;
+ }
+
+ protected function getAliases()
+ {
+ return [
+ CustomerTableMap::REF => "ref",
+ CustomerTableMap::LASTNAME => "last_name",
+ CustomerTableMap::FIRSTNAME => "first_name",
+ CustomerTableMap::EMAIL => "email",
+ CustomerTableMap::DISCOUNT => "discount",
+ CustomerTableMap::CREATED_AT => "sign_up_date",
+ "title_TITLE" => "title",
+ "address_TITLE" => "address_title",
+ "address_LABEL" => "label",
+ "address_IS_DEFAULT" => "is_default_address",
+ "address_COMPANY" => "company",
+ "address_ADDRESS1" => "address1",
+ "address_ADDRESS2" => "address2",
+ "address_ADDRESS3" => "address3",
+ "address_ZIPCODE" => "zipcode",
+ "address_CITY" => "city",
+ "address_COUNTRY" => "country",
+ "address_PHONE" => "phone",
+ "address_CELLPHONE" => "cellphone",
+ "address_FIRST_NAME" => "address_first_name",
+ "address_LAST_NAME" => "address_last_name",
+ "newsletter_IS_REGISTRED" => "is_registered_to_newsletter",
+ "order_TOTAL" => "total_orders",
+ "last_order_AMOUNT" => "last_order_amount",
+ "last_order_DATE" => "last_order_date",
+ ];
+ }
+
+ public function getOrder()
+ {
+ return [
+ "ref",
+ "title",
+ "last_name",
+ "first_name",
+ "email",
+ "discount",
+ "is_registered_to_newsletter",
+ "sign_up_date",
+ "total_orders",
+ "last_order_amount",
+ "last_order_date",
+ "label",
+ "address_title",
+ "address_first_name",
+ "address_last_name",
+ "company",
+ "address1",
+ "address2",
+ "address3",
+ "zipcode",
+ "city",
+ "country",
+ "phone",
+ "cellphone",
+ "is_default_address",
+ ];
+ }
+}
diff --git a/core/lib/Thelia/Tests/ImportExport/Export/CustomerExportTest.php b/core/lib/Thelia/Tests/ImportExport/Export/CustomerExportTest.php
new file mode 100644
index 000000000..52c7359f2
--- /dev/null
+++ b/core/lib/Thelia/Tests/ImportExport/Export/CustomerExportTest.php
@@ -0,0 +1,195 @@
+
+ */
+class CustomerExportTest extends \PHPUnit_Framework_TestCase
+{
+ public function testQuery()
+ {
+ new Translator(new Container());
+
+ $handler = new CustomerExport(new Container());
+
+ $lang = Lang::getDefaultLanguage();
+ $data = $handler->buildData($lang);
+
+ $keys = ["ref","title","last_name","first_name","email","label",
+ "discount","is_registered_to_newsletter","sign_up_date",
+ "total_orders","last_order_amount","last_order_date",
+ "address_first_name","address_last_name","company","address1",
+ "address2","address3","zipcode","city","country","phone",
+ "cellphone","is_default_address","address_title"];
+
+ sort($keys);
+
+ $rawData = $data->getData();
+
+ $max = CustomerQuery::create()->count();
+ /**
+ * 30 customers that has more than 1 addresses or enough
+ */
+ if (30 < $max) {
+ $max = 30;
+ }
+
+ for ($i = 0; $i < $max;) {
+ $row = $rawData[$i];
+
+ $rowKeys = array_keys($row);
+ sort($rowKeys);
+
+ $this->assertEquals($rowKeys, $keys);
+
+ $customer = CustomerQuery::create()
+ ->findOneByRef($row["ref"])
+ ;
+
+ $this->assertNotNull($customer);
+
+ $this->assertEquals($customer->getFirstname(), $row["first_name"]);
+ $this->assertEquals($customer->getLastname(), $row["last_name"]);
+ $this->assertEquals($customer->getEmail(), $row["email"]);
+ $this->assertEquals($customer->getCreatedAt()->format($lang->getDatetimeFormat()), $row["sign_up_date"]);
+ $this->assertEquals($customer->getDiscount(), $row["discount"]);
+
+ $title = CustomerTitleQuery::create()->findPk($customer->getTitleId());
+ $this->assertEquals($title->getShort(), $row["title"]);
+
+ $total = 0;
+ foreach ($customer->getOrders() as $order) {
+ $amount = $order->getTotalAmount($tax);
+
+ if (0 < $rate = $order->getCurrencyRate()) {
+ $amount = round($amount / $rate, 2);
+ }
+
+ $total += $amount;
+ }
+
+ $defaultCurrencyCode = Currency::getDefaultCurrency()->getCode();
+ $this->assertEquals($total . " " . $defaultCurrencyCode, $row["total_orders"]);
+
+ $lastOrder = OrderQuery::create()
+ ->filterByCustomer($customer)
+ ->orderByCreatedAt(Criteria::DESC)
+ ->findOne()
+ ;
+
+ if (null !== $lastOrder) {
+ $expectedPrice = $lastOrder->getTotalAmount($tax_) . " " . $lastOrder->getCurrency()->getCode();
+ $expectedDate = $lastOrder->getCreatedAt()->format($lang->getDatetimeFormat());
+ } else {
+ $expectedPrice = "";
+ $expectedDate = "";
+ }
+
+ $this->assertEquals(
+ $expectedPrice,
+ $row["last_order_amount"]
+ );
+
+ $this->assertEquals(
+ $expectedDate,
+ $row["last_order_date"]
+ );
+
+ $newsletter = NewsletterQuery::create()
+ ->findOneByEmail($customer->getEmail())
+ ;
+
+ $this->assertEquals(
+ $newsletter === null ? 0 : 1,
+ $row["is_registered_to_newsletter"]
+ );
+
+ do {
+ $address = AddressQuery::create()
+ ->filterByCustomer($customer)
+ ->filterByAddress1($rawData[$i]["address1"])
+ ->filterByAddress2($rawData[$i]["address2"])
+ ->_if(empty($rawData[$i]["address2"]))
+ ->_or()
+ ->filterByAddress2(null, Criteria::ISNULL)
+ ->_endif()
+ ->filterByAddress3($rawData[$i]["address3"])
+ ->_if(empty($rawData[$i]["address2"]))
+ ->_or()
+ ->filterByAddress2(null, Criteria::ISNULL)
+ ->_endif()
+ ->filterByFirstname($rawData[$i]["address_first_name"])
+ ->filterByLastname($rawData[$i]["address_last_name"])
+ ->filterByCountryId(
+ CountryI18nQuery::create()
+ ->filterByLocale($lang->getLocale())
+ ->findOneByTitle($rawData[$i]["country"])
+ ->getId()
+ )
+ ->filterByCompany($rawData[$i]["company"])
+ ->_if(empty($rawData[$i]["company"]))
+ ->_or()
+ ->filterByCompany(null, Criteria::ISNULL)
+ ->_endif()
+ ->filterByZipcode($rawData[$i]["zipcode"])
+ ->filterByCity($rawData[$i]["city"])
+ ->filterByIsDefault($rawData[$i]["is_default_address"])
+ ->filterByCellphone($rawData[$i]["cellphone"])
+ ->_if(empty($rawData[$i]["cellphone"]))
+ ->_or()
+ ->filterByCompany(null, Criteria::ISNULL)
+ ->_endif()
+ ->filterByPhone($rawData[$i]["phone"])
+ ->filterByLabel($rawData[$i]["label"])
+ ->filterByTitleId(
+ CustomerTitleI18nQuery::create()
+ ->filterByLocale($lang->getLocale())
+ ->findOneByShort($rawData[$i]["address_title"])
+ ->getId()
+ )
+ ->find()
+ ;
+
+ $this->assertEquals(1, $address->count());
+
+ $rowKeys = array_keys($rawData[$i]);
+ sort($rowKeys);
+
+ $this->assertEquals($rowKeys, $keys);
+
+ ++$i;
+ } while (
+ isset($rawData[$i]["ref"]) &&
+ $rawData[$i-1]["ref"] === $rawData[$i]["ref"] &&
+ ++$max
+ );
+
+ }
+ }
+}
\ No newline at end of file