* @copyright 2007-2015 PrestaShop SA * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) * International Registered Trademark & Property of PrestaShop SA */ class AddressFormatCore extends ObjectModel { /** @var int */ public $id_address_format; /** @var int */ public $id_country; /** @var string */ public $format; protected $_errorFormatList = array(); /** * @see ObjectModel::$definition */ public static $definition = array( 'table' => 'address_format', 'primary' => 'id_country', 'fields' => array( 'format' => array('type' => self::TYPE_HTML, 'validate' => 'isGenericName', 'required' => true), 'id_country' => array('type' => self::TYPE_INT), ), ); public static $requireFormFieldsList = array( 'firstname', 'lastname', 'address1', 'city', 'Country:name'); public static $forbiddenPropertyList = array( 'deleted', 'date_add', 'alias', 'secure_key', 'note', 'newsletter', 'ip_registration_newsletter', 'newsletter_date_add', 'optin', 'passwd', 'last_passwd_gen', 'active', 'is_guest', 'date_upd', 'country', 'years', 'days', 'months', 'description', 'meta_description', 'short_description', 'link_rewrite', 'meta_title', 'meta_keywords', 'display_tax_label', 'need_zip_code', 'contains_states', 'call_prefixes', 'show_public_prices', 'max_payment', 'max_payment_days', 'geoloc_postcode', 'logged', 'account_number', 'groupBox', 'ape', 'max_payment', 'outstanding_allow_amount', 'call_prefix', 'definition', 'debug_list' ); public static $forbiddenClassList = array( 'Manufacturer', 'Supplier'); const _CLEANING_REGEX_ = '#([^\w:_]+)#i'; /* * Check if the the association of the field name and a class name * is valide * @className is the name class * @fieldName is a property name * @isIdField boolean to know if we have to allowed a property name started by 'id_' */ protected function _checkValidateClassField($className, $fieldName, $isIdField) { $isValide = false; if (!class_exists($className)) { $this->_errorFormatList[] = Tools::displayError('This class name does not exist.'). ': '.$className; } else { $obj = new $className(); $reflect = new ReflectionObject($obj); // Check if the property is accessible $publicProperties = $reflect->getProperties(ReflectionProperty::IS_PUBLIC); foreach ($publicProperties as $property) { $propertyName = $property->getName(); if (($propertyName == $fieldName) && ($isIdField || (!preg_match('/\bid\b|id_\w+|\bid[A-Z]\w+/', $propertyName)))) { $isValide = true; } } if (!$isValide) { $this->_errorFormatList[] = Tools::displayError('This property does not exist in the class or is forbidden.'). ': '.$className.': '.$fieldName; } unset($obj); unset($reflect); } return $isValide; } /* * Verify the existence of a field name and check the availability * of an association between a field name and a class (ClassName:fieldName) * if the separator is overview * @patternName is the composition of the class and field name * @fieldsValidate contains the list of available field for the Address class */ protected function _checkLiableAssociation($patternName, $fieldsValidate) { $patternName = trim($patternName); if ($associationName = explode(':', $patternName)) { $totalNameUsed = count($associationName); if ($totalNameUsed > 2) { $this->_errorFormatList[] = Tools::displayError('This association has too many elements.'); } elseif ($totalNameUsed == 1) { $associationName[0] = strtolower($associationName[0]); if (in_array($associationName[0], self::$forbiddenPropertyList) || !$this->_checkValidateClassField('Address', $associationName[0], false)) { $this->_errorFormatList[] = Tools::displayError('This name is not allowed.').': '. $associationName[0]; } } elseif ($totalNameUsed == 2) { if (empty($associationName[0]) || empty($associationName[1])) { $this->_errorFormatList[] = Tools::displayError('Syntax error with this pattern.').': '.$patternName; } else { $associationName[0] = ucfirst($associationName[0]); $associationName[1] = strtolower($associationName[1]); if (in_array($associationName[0], self::$forbiddenClassList)) { $this->_errorFormatList[] = Tools::displayError('This name is not allowed.').': '. $associationName[0]; } else { // Check if the id field name exist in the Address class // Don't check this attribute on Address (no sense) if ($associationName[0] != 'Address') { $this->_checkValidateClassField('Address', 'id_'.strtolower($associationName[0]), true); } // Check if the field name exist in the class write by the user $this->_checkValidateClassField($associationName[0], $associationName[1], false); } } } } } /* * Check if the set fields are valide */ public function checkFormatFields() { $this->_errorFormatList = array(); $fieldsValidate = Address::getFieldsValidate(); $usedKeyList = array(); $multipleLineFields = explode("\n", $this->format); if ($multipleLineFields && is_array($multipleLineFields)) { foreach ($multipleLineFields as $lineField) { if (($patternsName = preg_split(self::_CLEANING_REGEX_, $lineField, -1, PREG_SPLIT_NO_EMPTY))) { if (is_array($patternsName)) { foreach ($patternsName as $patternName) { if (!in_array($patternName, $usedKeyList)) { $this->_checkLiableAssociation($patternName, $fieldsValidate); $usedKeyList[] = $patternName; } else { $this->_errorFormatList[] = Tools::displayError('This key has already been used.'). ': '.$patternName; } } } } } } return (count($this->_errorFormatList)) ? false : true; } /* * Returns the error list */ public function getErrorList() { return $this->_errorFormatList; } /* ** Set the layout key with the liable value ** example : (firstname) => 'Presta' will result (Presta) ** : (firstname-lastname) => 'Presta' and 'Shop' result '(Presta-Shop)' */ protected static function _setOriginalDisplayFormat(&$formattedValueList, $currentLine, $currentKeyList) { if ($currentKeyList && is_array($currentKeyList)) { if ($originalFormattedPatternList = explode(' ', $currentLine)) { // Foreach the available pattern foreach ($originalFormattedPatternList as $patternNum => $pattern) { // Var allows to modify the good formatted key value when multiple key exist into the same pattern $mainFormattedKey = ''; // Multiple key can be found in the same pattern foreach ($currentKeyList as $key) { // Check if we need to use an older modified pattern if a key has already be matched before $replacedValue = empty($mainFormattedKey) ? $pattern : $formattedValueList[$mainFormattedKey]; $chars = $start = $end = str_replace($key, '', $replacedValue); if (preg_match(self::_CLEANING_REGEX_, $chars)) { if (Tools::substr($replacedValue, 0, Tools::strlen($chars)) == $chars) { $end = ''; } else { $start = ''; } if ($chars) { $replacedValue = str_replace($chars, '', $replacedValue); } } if ($formattedValue = preg_replace('/^'.$key.'$/', $formattedValueList[$key], $replacedValue, -1, $count)) { if ($count) { // Allow to check multiple key in the same pattern, if (empty($mainFormattedKey)) { $mainFormattedKey = $key; } // Set the pattern value to an empty string if an older key has already been matched before if ($mainFormattedKey != $key) { $formattedValueList[$key] = ''; } // Store the new pattern value $formattedValueList[$mainFormattedKey] = $start.$formattedValue.$end; unset($originalFormattedPatternList[$patternNum]); } } } } } } } /* ** Cleaned the layout set by the user */ public static function cleanOrderedAddress(&$orderedAddressField) { foreach ($orderedAddressField as &$line) { $cleanedLine = ''; if (($keyList = preg_split(self::_CLEANING_REGEX_, $line, -1, PREG_SPLIT_NO_EMPTY))) { foreach ($keyList as $key) { $cleanedLine .= $key.' '; } $cleanedLine = trim($cleanedLine); $line = $cleanedLine; } } } /* * Returns the formatted fields with associated values * * @address is an instancied Address object * @addressFormat is the format * @return double Array */ public static function getFormattedAddressFieldsValues($address, $addressFormat, $id_lang = null) { if (!$id_lang) { $id_lang = Context::getContext()->language->id; } $tab = array(); $temporyObject = array(); // Check if $address exist and it's an instanciate object of Address if ($address && ($address instanceof Address)) { foreach ($addressFormat as $line) { if (($keyList = preg_split(self::_CLEANING_REGEX_, $line, -1, PREG_SPLIT_NO_EMPTY)) && is_array($keyList)) { foreach ($keyList as $pattern) { if ($associateName = explode(':', $pattern)) { $totalName = count($associateName); if ($totalName == 1 && isset($address->{$associateName[0]})) { $tab[$associateName[0]] = $address->{$associateName[0]}; } else { $tab[$pattern] = ''; // Check if the property exist in both classes if (($totalName == 2) && class_exists($associateName[0]) && property_exists($associateName[0], $associateName[1]) && property_exists($address, 'id_'.strtolower($associateName[0]))) { $idFieldName = 'id_'.strtolower($associateName[0]); if (!isset($temporyObject[$associateName[0]])) { $temporyObject[$associateName[0]] = new $associateName[0]($address->{$idFieldName}); } if ($temporyObject[$associateName[0]]) { $tab[$pattern] = (is_array($temporyObject[$associateName[0]]->{$associateName[1]})) ? ((isset($temporyObject[$associateName[0]]->{$associateName[1]}[$id_lang])) ? $temporyObject[$associateName[0]]->{$associateName[1]}[$id_lang] : '') : $temporyObject[$associateName[0]]->{$associateName[1]}; } } } } } AddressFormat::_setOriginalDisplayFormat($tab, $line, $keyList); } } } AddressFormat::cleanOrderedAddress($addressFormat); // Free the instanciate objects foreach ($temporyObject as &$object) { unset($object); } return $tab; } /** * Generates the full address text * * @param Address $address * @param array $patternRules A defined rules array to avoid some pattern * @param string $newLine A string containing the newLine format * @param string $separator A string containing the separator format * @param array $style * @return string */ public static function generateAddress(Address $address, $patternRules = array(), $newLine = "\r\n", $separator = ' ', $style = array()) { $addressFields = AddressFormat::getOrderedAddressFields($address->id_country); $addressFormatedValues = AddressFormat::getFormattedAddressFieldsValues($address, $addressFields); $addressText = ''; foreach ($addressFields as $line) { if (($patternsList = preg_split(self::_CLEANING_REGEX_, $line, -1, PREG_SPLIT_NO_EMPTY))) { $tmpText = ''; foreach ($patternsList as $pattern) { if ((!array_key_exists('avoid', $patternRules)) || (array_key_exists('avoid', $patternRules) && !in_array($pattern, $patternRules['avoid']))) { $tmpText .= (isset($addressFormatedValues[$pattern]) && !empty($addressFormatedValues[$pattern])) ? (((isset($style[$pattern])) ? (sprintf($style[$pattern], $addressFormatedValues[$pattern])) : $addressFormatedValues[$pattern]).$separator) : ''; } } $tmpText = trim($tmpText); $addressText .= (!empty($tmpText)) ? $tmpText.$newLine: ''; } } $addressText = preg_replace('/'.preg_quote($newLine, '/').'$/i', '', $addressText); $addressText = rtrim($addressText, $separator); return $addressText; } public static function generateAddressSmarty($params, &$smarty) { return AddressFormat::generateAddress( $params['address'], (isset($params['patternRules']) ? $params['patternRules'] : array()), (isset($params['newLine']) ? $params['newLine'] : "\r\n"), (isset($params['separator']) ? $params['separator'] : ' '), (isset($params['style']) ? $params['style'] : array()) ); } /** * Returns selected fields required for an address in an array according to a selection hash * @return array String values */ public static function getValidateFields($className) { $propertyList = array(); if (class_exists($className)) { $object = new $className(); $reflect = new ReflectionObject($object); // Check if the property is accessible $publicProperties = $reflect->getProperties(ReflectionProperty::IS_PUBLIC); foreach ($publicProperties as $property) { $propertyName = $property->getName(); if ((!in_array($propertyName, AddressFormat::$forbiddenPropertyList)) && (!preg_match('#id|id_\w#', $propertyName))) { $propertyList[] = $propertyName; } } unset($object); unset($reflect); } return $propertyList; } /* * Return a list of liable class of the className */ public static function getLiableClass($className) { $objectList = array(); if (class_exists($className)) { $object = new $className(); $reflect = new ReflectionObject($object); // Get all the name object liable to the Address class $publicProperties = $reflect->getProperties(ReflectionProperty::IS_PUBLIC); foreach ($publicProperties as $property) { $propertyName = $property->getName(); if (preg_match('#id_\w#', $propertyName) && strlen($propertyName) > 3) { $nameObject = ucfirst(substr($propertyName, 3)); if (!in_array($nameObject, self::$forbiddenClassList) && class_exists($nameObject)) { $objectList[$nameObject] = new $nameObject(); } } } unset($object); unset($reflect); } return $objectList; } /** * Returns address format fields in array by country * * @param int $id_country If null using PS_COUNTRY_DEFAULT * @param bool $split_all * @param bool $cleaned * @return Array String field address format */ public static function getOrderedAddressFields($id_country = 0, $split_all = false, $cleaned = false) { $out = array(); $field_set = explode("\n", AddressFormat::getAddressCountryFormat($id_country)); foreach ($field_set as $field_item) { if ($split_all) { if ($cleaned) { $keyList = ($cleaned) ? preg_split(self::_CLEANING_REGEX_, $field_item, -1, PREG_SPLIT_NO_EMPTY) : explode(' ', $field_item); } foreach ($keyList as $word_item) { $out[] = trim($word_item); } } else { $out[] = ($cleaned) ? implode(' ', preg_split(self::_CLEANING_REGEX_, trim($field_item), -1, PREG_SPLIT_NO_EMPTY)) : trim($field_item); } } return $out; } /* ** Return a data array containing ordered, formatedValue and object fields */ public static function getFormattedLayoutData($address) { $layoutData = array(); if ($address && $address instanceof Address) { $layoutData['ordered'] = AddressFormat::getOrderedAddressFields((int)$address->id_country); $layoutData['formated'] = AddressFormat::getFormattedAddressFieldsValues($address, $layoutData['ordered']); $layoutData['object'] = array(); $reflect = new ReflectionObject($address); $public_properties = $reflect->getProperties(ReflectionProperty::IS_PUBLIC); foreach ($public_properties as $property) { if (isset($address->{$property->getName()})) { $layoutData['object'][$property->getName()] = $address->{$property->getName()}; } } } return $layoutData; } /** * Returns address format by country if not defined using default country * * @param int $id_country * @return String field address format */ public static function getAddressCountryFormat($id_country = 0) { $id_country = (int)$id_country; $tmp_obj = new AddressFormat(); $tmp_obj->id_country = $id_country; $out = $tmp_obj->getFormat($tmp_obj->id_country); unset($tmp_obj); return $out; } /** * Returns address format by country * * @param int $id_country * @return String field address format */ public function getFormat($id_country) { $out = $this->_getFormatDB($id_country); if (empty($out)) { $out = $this->_getFormatDB(Configuration::get('PS_COUNTRY_DEFAULT')); } return $out; } protected function _getFormatDB($id_country) { if (!Cache::isStored('AddressFormat::_getFormatDB'.$id_country)) { $format = Db::getInstance()->getValue(' SELECT format FROM `'._DB_PREFIX_.$this->def['table'].'` WHERE `id_country` = '.(int)$id_country); $format = trim($format); Cache::store('AddressFormat::_getFormatDB'.$id_country, $format); return $format; } return Cache::retrieve('AddressFormat::_getFormatDB'.$id_country); } public static function getFieldsRequired() { $address = new Address; return array_unique(array_merge($address->getFieldsRequiredDB(), AddressFormat::$requireFormFieldsList)); } }