Sort algorithm: Magento checkout totals sorted wrongly causing wrong shipping tax calculation(排序算法:Magento 结帐总额排序错误导致运费计算错误)
问题描述
在 Magento 中有一个功能,您可以通过指定运行总计之前和之后的总计来定义总计计算的顺序.
我添加了一个自定义总数,如果我将以下几行添加到 config.xml,排序是错误的.错误的意思是:tax_shipping before shipping.这会导致运费税金增加两次.
但这违反了条件
tax_shipping之后:航运我的猜测:在全套规则中肯定有一些矛盾.但是我怎么才能找到它呢?
这是我添加的唯一规则.如果没有此规则,
tax_shipping将排在shipping之后.<class>n98_shippingprotection/quote_address_total_shippingprotectionTax</class><after>小计,折扣,运费,税</after><before>grand_total</before></shippingprotectiontax> 下面我将 usort 调用返回的排序数组粘贴到
Mage_Sales_Model_Quote_Address_Total_Collector::_getSortedCollectorCodes()对于没有安装Magento的,代码是这样的:/*** uasort回调函数** @param 数组 $a* @param 数组 $b* @return 整数*/受保护的函数 _compareTotals($a, $b){$aCode = $a['_code'];$bCode = $b['_code'];if (in_array($aCode, $b['after']) || in_array($bCode, $a['before'])) {$res = -1;} elseif (in_array($bCode, $a['after']) || in_array($aCode, $b['before'])) {$res = 1;} 别的 {$res = 0;}返回 $res;}受保护的函数 _getSortedCollectorCodes(){...uasort($configArray, array($this, '_compareTotals'));Mage::log('排序:');//这会产生下面的输出$loginfo = "";foreach($configArray as $code=>$data) {$loginfo .= "$code ";$loginfo .= "after: ".implode(',',$data['after'])." ";$loginfo .= "before: ".implode(',',$data['before'])." ";$loginfo .= " ";}法师::日志($loginfo);...日志输出:
名义后:之前:小计,grand_total小计之后:名义上之前:总计,运费,免运费,税小计,折扣,税,小,礼品包装,现金交付,现金交付_税,运输保护,运输保护税免运费之后:小计,名义之前:tax_subtotal,shipping,grand_total,tax,discounttax_shipping之后:运费,小计,免运费,tax_subtotal,名义之前:税收,折扣,grand_total,grand_total礼品包装之后:小计,名义前:tax_小计之后:免费送货,小计,小计,名义之前:税收,折扣,运费,grand_total,weee,customerbalance,giftcardaccount,reward呜呜呜之后:小计,tax_subtotal,名义,免费送货,小计,小计,名义之前:税收,折扣,grand_total,grand_total,税收运输之后:小计,免运费,tax_subtotal,名义之前:grand_total,discount,tax_shipping,tax,cashondelivery,cashondelivery_tax,shippingprotection,shippingprotectiontax折扣之后:小计,运费,名义,免费送货,tax_subtotal,tax_shipping,weee之前:grand_total,tax,customerbalance,giftcardaccount,reward,cashondelivery,cashondelivery_tax,shippingprotection,shippingprotectiontax货到付款之后:小计,折扣,运费,名义,小计,运费,名义,免运费,tax_subtotal,tax_shipping,小,小计,免运费,tax_subtotal,名义之前:税,总金额,总金额,客户余额,礼品卡帐户,税金包装,奖励,客户余额,礼品卡帐户,奖励运输保护之后:小计,折扣,运费,名义,小计,运费,名义,免运费,tax_subtotal,tax_shipping,小,小计,免运费,tax_subtotal,名义之前:税,总金额,总金额,客户余额,礼品卡帐户,税金礼品包装,奖励,现金交付_税,客户余额,礼品卡帐户,奖励税之后:小计,运费,折扣,tax_subtotal,免费送货,tax_shipping,名义,小,现金交付,shippingprotection之前:grand_total,customerbalance,giftcardaccount,tax_giftwrapping,reward,cashondelivery_tax,shippingprotectiontax运输保护税之后:小计,折扣,运费,税,名义,小计,运费,名义,免费送货,tax_subtotal,tax_shipping,小,小计,免费送货,tax_subtotal,名义,小计,运费,折扣,tax_subtotal,免费送货,tax_shipping,名义,weee,货到付款,运输保障之前:grand_total,customerbalance,giftcardaccount,rewardcashondelivery_tax之后:小计,折扣,运费,税,名义,小计,运费,名义,免费送货,tax_subtotal,tax_shipping,小,小计,免费送货,tax_subtotal,名义,小计,运费,折扣,tax_subtotal,免费送货,tax_shipping,名义,weee,货到付款之前:grand_total,customerbalance,giftcardaccount,rewardtax_giftwrapping后:税,小计,运费,折扣,tax_subtotal,免费送货,tax_shipping,名义,小之前:grand_total、customerbalance、giftcardaccount累计之后:小计、名义、运费、免运费、tax_subtotal、折扣、税、tax_giftwrapping、cashondelivery、cashondelivery_tax、shippingprotection、shippingprotectiontax之前:客户余额,礼品卡帐户,奖励报酬之后:wee,discount,tax,tax_subtotal,grand_total,subtotal,shipping,nominal,freeshipping,tax_subtotal,tax_shipping,weee,subtotal,shipping,discount,tax_subtotal,freeshipping,tax_shipping,nominal,weee,freeshipping,subtotal,subtotal,nominal小计,名义,运费,免运费,tax_subtotal,折扣,tax,tax_giftwrapping之前:礼品卡帐户,客户余额,客户余额礼品卡账户之后:小,折扣,税收,tax_subtotal,grand_total,奖励,小计,运费,名义,免费送货,tax_shipping,weee之前:客户余额客户余额之后:wee,discount,tax,tax_subtotal,grand_total,reward,giftcardaccount,subtotal,shipping,nominal,freeshipping,tax_shipping,weee前:在 Vinai 的回答之后,我添加了更多调试代码
$fp = fopen('/tmp/dotfile','w');fwrite($fp,"digraph TotalOrder ");fwrite($fp,"{ ");foreach($configArray as $code=>$data) {$_code = $data['_code'];foreach($data['before'] 作为 $beforeCode) {fwrite($fp,"$beforeCode -> $_code; ");}foreach($data['after'] 作为 $afterCode) {fwrite($fp,"$_code -> $afterCode; ");}}fwrite($fp,"} ");fclose($fp);并使用graphviz对其进行可视化:
dot -Tpng dotfile >即.png.这是第一次尝试的结果.排序后调用.编辑 2:
我觉得这没什么用.
所以我在合并之后/之前条目之前对数组进行了可视化.(紧跟在
$configArray = $this->_modelsConfig;之后)这是没有我的
shippingprotectiontax条目:这是我的
shippingprotectiontax条目:我没有看到任何明显的矛盾.
编辑 3:
uasort 之前的配置数组:
<前>大批 ('名义' =>大批 ('class' => 'sales/quote_address_total_nominal','之前' =>大批 (0 => '小计',1 => 'grand_total',),'renderer' => 'checkout/total_nominal','之后' =>大批 (),'_code' => '名义',),'小计' =>大批 ('class' => 'sales/quote_address_total_subtotal','之后' =>大批 (0 => '名义',),'之前' =>大批 (0 => 'grand_total',1 => '运输',2 => '免费送货',3 => 'tax_subtotal',4 => '折扣',5 => '税',6 => '小',7 => '礼品包装',8 => '现金交付',9 => 'cashondelivery_tax',10 => '运输保护',11 => 'shippingprotectiontax',),'renderer' => 'tax/checkout_subtotal','admin_renderer' => 'adminhtml/sales_order_create_totals_subtotal','_code' => '小计',),'运输' =>大批 ('class' => 'sales/quote_address_total_shipping','之后' =>大批 (0 => '小计',1 => '免费送货',2 => 'tax_subtotal',3 => '名义',),'之前' =>大批 (0 => 'grand_total',1 => '折扣',2 => 'tax_shipping',3 => '税',4 => '现金交付',5 => 'cashondelivery_tax',6 => '运输保护',7 => 'shippingprotectiontax',),'renderer' => 'tax/checkout_shipping','admin_renderer' => 'adminhtml/sales_order_create_totals_shipping','_code' => '运输',),'grand_total' =>大批 ('class' => 'sales/quote_address_total_grand','之后' =>大批 (0 => '小计',1 => '名义',2 => '运输',3 => '免费送货',4 => 'tax_subtotal',5 => '折扣',6 => '税',7 => 'tax_giftwrapping',8 => '现金交付',9 => 'cashondelivery_tax',10 => '运输保护',11 => 'shippingprotectiontax',),'renderer' => 'tax/checkout_grandtotal','admin_renderer' => 'adminhtml/sales_order_create_totals_grandtotal','之前' =>大批 (0 => '客户余额',1 => '礼品卡账户',2 => '奖励',),'_code' => 'grand_total',),'免费送货' =>大批 ('class' => 'salesrule/quote_freeshipping','之后' =>大批 (0 => '小计',1 => '名义',),'之前' =>大批 (0 => 'tax_subtotal',1 => '运输',2 => 'grand_total',3 => '税',4 => '折扣',),'_code' => '免费送货',),'折扣' =>大批 ('class' => 'salesrule/quote_discount','之后' =>大批 (0 => '小计',1 => '运输',2 => '名义',3 => '免费送货',4 => 'tax_subtotal',5 => 'tax_shipping',6 => '小',),'之前' =>大批 (0 => 'grand_total',1 => '税',2 => '客户余额',3 => '礼品卡账户',4 => '奖励',5 => '现金交付',6 => 'cashondelivery_tax',7 => '运输保护',8 => 'shippingprotectiontax',),'renderer' => 'tax/checkout_discount','admin_renderer' => 'adminhtml/sales_order_create_totals_discount','_code' => '折扣',),'tax_subtotal' =>大批 ('class' => 'tax/sales_total_quote_subtotal','之后' =>大批 (0 => '免费送货',1 => '小计',2 => '小计',3 => '名义',),'之前' =>大批 (0 => '税',1 => '折扣',2 => '运输',3 => 'grand_total',4 => '小',5 => '客户余额',6 => '礼品卡账户',7 => '奖励',),'_code' => 'tax_subtotal',),'tax_shipping' =>大批 ('class' => 'tax/sales_total_quote_shipping','之后' =>大批 (0 => '运输',1 => '小计',2 => '免费送货',3 => 'tax_subtotal',4 => '名义',),'之前' =>大批 (0 => '税',1 => '折扣',2 => 'grand_total',3 => 'grand_total',),'_code' => 'tax_shipping',),'税' =>大批 ('class' => 'tax/sales_total_quote_tax','之后' =>大批 (0 => '小计',1 => '运输',2 => '折扣',3 => 'tax_subtotal',4 => '免费送货',5 => 'tax_shipping',6 => '名义',7 => '小',8 => '现金交付',9 => '运输保护',),'之前' =>大批 (0 => 'grand_total',1 => '客户余额',2 => '礼品卡账户',3 => 'tax_giftwrapping',4 => '奖励',5 => 'cashondelivery_tax',6 => 'shippingprotectiontax',),'renderer' => 'tax/checkout_tax','admin_renderer' => 'adminhtml/sales_order_create_totals_tax','_code' => '税',),'小' =>大批 ('class' => 'weee/total_quote_weee','之后' =>大批 (0 => '小计',1 => 'tax_subtotal',2 => '名义',3 => '免费送货',4 => '小计',5 => '小计',6 => '名义',),'之前' =>大批 (0 => '税',1 => '折扣',2 => 'grand_total',3 => 'grand_total',4 => '税',),'_code' => 'weee',),'客户余额' =>大批 ('class' => 'enterprise_customerbalance/total_quote_customerbalance','之后' =>大批 (0 => '小',1 => '折扣',2 => '税',3 => 'tax_subtotal',4 => 'grand_total',5 => '奖励',6 => '礼品卡账户',7 => '小计',8 => '运输',9 => '名义',10 => '免费送货',11 => 'tax_shipping',12 => '小',),'renderer' => 'enterprise_customerbalance/checkout_total','之前' =>大批 (),'_code' => '客户余额',),'礼品卡帐户' =>大批 ('class' => 'enterprise_giftcardaccount/total_quote_giftcardaccount','之后' =>大批 (0 => '小',1 => '折扣',2 => '税',3 => 'tax_subtotal',4 => 'grand_total',5 => '奖励',6 => '小计',7 => '运输',8 => '名义',9 => '免费送货',11 => 'tax_shipping',12 => '小',),'之前' =>大批 (0 => '客户余额',),'renderer' => 'enterprise_giftcardaccount/checkout_cart_total','_code' => 'giftcardaccount',),'礼品包装' =>大批 ('class' => 'enterprise_giftwrapping/total_quote_giftwrapping','之后' =>大批 (0 => '小计',1 => '名义',),'renderer' => 'enterprise_giftwrapping/checkout_totals','之前' =>大批 (),'_code' => '礼品包装',),'tax_giftwrapping' =>大批 ('class' => 'enterprise_giftwrapping/total_quote_tax_giftwrapping','之后' =>大批 (0 => '税',1 => '小计',2 => '运输',3 => '折扣',4 => 'tax_subtotal',5 => '免费送货',6 => 'tax_shipping',7 => '名义',8 => '小',),'之前' =>大批 (0 => 'grand_total',1 => '客户余额',2 => '礼品卡账户',),'_code' => 'tax_giftwrapping',),'奖励' =>大批 ('class' => 'enterprise_reward/total_quote_reward','之后' =>大批 (0 => '小',1 => '折扣',2 => '税',3 => 'tax_subtotal',4 => 'grand_total',5 => '小计',6 => '运输',7 => '名义',8 => '免费送货',9 => 'tax_subtotal',10 => 'tax_shipping',11 => '小',12 => '小计',13 => '运输',14 => '折扣',15 => 'tax_subtotal',16 => '免费送货',17 => 'tax_shipping',18 => '名义',19 => '小',20 => '免费送货',21 => '小计',22 => '小计',23 => '名义',24 => '小计',25 => '名义',26 => '运输',27 => '免费送货',28 => 'tax_subtotal',29 => '折扣',30 => '税',31 => 'tax_giftwrapping',),'之前' =>大批 (0 => '礼品卡账户',1 => '客户余额',2 => '客户余额',),'renderer' => 'enterprise_reward/checkout_total','_code' => '奖励',),'现金交付' =>大批 ('class' => 'cashondelivery/quote_total','之后' =>大批 (0 => '小计',1 => '折扣',2 => '运输',3 => '名义',4 => '小计',5 => '运输',6 => '名义',7 => '免费送货',8 => 'tax_subtotal',9 => 'tax_shipping',10 => '小',11 => '小计',12 => '免费送货',13 => 'tax_subtotal',14 => '名义',),'之前' =>大批 (0 => '税',1 => 'grand_total',2 => 'grand_total',3 => '客户余额',4 => '礼品卡账户',5 => 'tax_giftwrapping',6 => '奖励',7 => '客户余额',8 => '礼品卡账户',9 => '奖励',),'renderer' => 'cashondelivery/checkout_cod','admin_renderer' => 'cashondelivery/adminhtml_sales_order_create_totals_cod','_code' => '现金交货',),'cashondelivery_tax' =>大批 ('class' => 'cashondelivery/quote_taxTotal','之后' =>大批 (0 => '小计',1 => '折扣',2 => '运输',3 => '税',4 => '名义',5 => '小计',6 => '运输',7 => '名义',8 => '免费送货',9 => 'tax_subtotal',10 => 'tax_shipping',11 => '小',12 => '小计',13 => '免费送货',14 => 'tax_subtotal',15 => '名义',16 => '小计',17 => '运输',18 => '折扣',19 => 'tax_subtotal',20 => '免费送货',21 => 'tax_shipping',22 => '名义',23 => '小',24 => '现金交付',),'之前' =>大批 (0 => 'grand_total',1 => '客户余额',2 => '礼品卡账户',3 => '奖励',),'_code' => 'cashondelivery_tax',),'运输保护' =>大批 ('class' => 'n98_shippingprotection/quote_address_total_shippingprotection','之后' =>大批 (0 => '小计',1 => '折扣',2 => '运输',3 => '名义',4 => '小计',5 => '运输',6 => '名义',7 => '免费送货',8 => 'tax_subtotal',9 => 'tax_shipping',10 => '小',11 => '小计',12 => '免费送货',13 => 'tax_subtotal',14 => '名义',),'之前' =>大批 (0 => '税',1 => 'grand_total',2 => 'grand_total',3 => '客户余额',4 => '礼品卡账户',5 => 'tax_giftwrapping',6 => '奖励',7 => 'cashondelivery_tax',8 => '客户余额',9 => '礼品卡账户',10 => '奖励',),'_code' => 'shippingprotection',),'运输保护税' =>大批 ('class' => 'n98_shippingprotection/quote_address_total_shippingprotectionTax','之后' =>大批 (0 => '小计',1 => '折扣',2 => '运输',3 => '税',4 => '名义',5 => '小计',6 => '运输',7 => '名义',8 => '免费送货',9 => 'tax_subtotal',10 => 'tax_shipping',11 => '小',12 => '小计',13 => '免费送货',14 => 'tax_subtotal',15 => '名义',16 => '小计',17 => '运输',18 => '折扣',19 => 'tax_subtotal',20 => '免费送货',21 => 'tax_shipping',22 => '名义',23 => '小',24 => '现金交付',25 => '运输保护',),'之前' =>大批 (0 => 'grand_total',1 => '客户余额',2 => '礼品卡账户',3 => '奖励',),'_code' => 'shippingprotectiontax',),)<小时>
更新: Magento 错误票证:https://jira.magento.com/browse/MCACE-129
解决方案最后,这是我针对这个问题的补丁.
它实现了 Vinai 建议的拓扑排序.
- 将
app/code/core/Mage/Sales/Model/Config/Ordered.php复制到app/code/local/Mage/Sales/Model/Config/Ordered.php- 将补丁的内容保存到文件
total-sorting.patch并调用patch -p0 app/code/local/Mage/Sales/Model/Config/Ordered.php如果升级,请确保重新应用这些步骤.
该补丁经过测试可与 Magento 1.7.0.2 配合使用
<前>--- app/code/core/Mage/Sales/Model/Config/Ordered.php 2012-08-14 14:19:50.306504947 +0200+++ app/code/local/Mage/Sales/Model/Config/Ordered.php 2012-08-15 10:00:47.027003404 +0200@@ -121,6 +121,78 @@返回 $totalConfig;}+//[补丁代码开始]++/**+ * 拓扑排序+ *+ * 版权所有:http://www.calcatraz.com/blog/php-topological-sort-function-384+ * 并修复见 http://stackoverflow.com/questions/11953021/topological-sorting-in-php 上的评论+ *+ * @param $nodeids 节点 ID+ * @param $edges 边数组.每条边被指定为一个包含两个元素的数组:边的源节点和目标节点+ * @return 数组|null+ */+ 函数拓扑排序($nodeids,$edges){+ $L = $S = $nodes = array();+ foreach($nodeids 作为 $id) {+ $nodes[$id] = array('in'=>array(), 'out'=>array());+ foreach($edges as $e) {+ if ($id==$e[0]) { $nodes[$id]['out'][]=$e[1];}+ if ($id==$e[1]) { $nodes[$id]['in'][]=$e[0];}+ }+ }+ foreach ($nodes as $id=>$n) { if (empty($n['in'])) $S[]=$id;}+ while ($id = array_shift($S)) {+ if (!in_array($id, $L)) {+ $L[] = $id;+ foreach($nodes[$id]['out'] as $m) {+ $nodes[$m]['in'] = array_diff($nodes[$m]['in'], array($id));+ if (empty($nodes[$m]['in'])) { $S[] = $m;}+ }+ $nodes[$id]['out'] = array();+ }+ }+ foreach($nodes as $n) {+ if (!empty($n['in']) 或 !empty($n['out'])) {+ 返回空;//不可排序,因为图是循环的+ }+ }+ 返回 $L;+ }++/**+ * 排序配置数组+ *+ * public 可以通过测试轻松访问+ *+ * @param $configArray+ * @return 数组+ */+ 公共函数 _topSortConfigArray($configArray)+ {+ $nodes = array_keys($configArray);+ $edges = 数组();++ foreach ($configArray as $code => $data) {+ $_code = $data['_code'];+ if (!isset($configArray[$_code])) 继续;+ foreach ($data['before'] as $beforeCode) {+ if (!isset($configArray[$beforeCode])) 继续;+ $edges[] = array($_code, $beforeCode);+ }++ foreach ($data['after'] as $afterCode) {+ if (!isset($configArray[$afterCode])) 继续;+ $edges[] = array($afterCode, $_code);+ }+ }+ 返回 $this->topological_sort($nodes, $edges);+ }++//[补丁代码结束]++/*** 汇总所有项目的前后信息并根据此数据对总数进行排序*@@ -138,38 +210,16 @@//如果第一个元素包含sort_order"键,则调用简单排序重置($configArray);$element = current($configArray);+//[补丁代码开始]如果 (isset($element['sort_order'])) {uasort($configArray, array($this, '_compareSortOrder'));+ $sortedCollectors = array_keys($configArray);+} 别的 {- foreach ($configArray as $code => $data) {- foreach ($data['before'] as $beforeCode) {- 如果 (!isset($configArray[$beforeCode])) {- 继续;- }- $configArray[$code]['before'] = array_unique(array_merge(- $configArray[$code]['before'], $configArray[$beforeCode]['before']- ));- $configArray[$beforeCode]['after'] = array_merge(- $configArray[$beforeCode]['after'], array($code), $data['after']- );- $configArray[$beforeCode]['after'] = array_unique($configArray[$beforeCode]['after']);- }- foreach ($data['after'] as $afterCode) {- 如果 (!isset($configArray[$afterCode])) {- 继续;- }- $configArray[$code]['after'] = array_unique(array_merge(- $configArray[$code]['after'], $configArray[$afterCode]['after']- ));- $configArray[$afterCode]['before'] = array_merge(- $configArray[$afterCode]['before'], array($code), $data['before']- );- $configArray[$afterCode]['before'] = array_unique($configArray[$afterCode]['before']);- }- }- uasort($configArray, array($this, '_compareTotals'));+ $sortedCollectors = $this->_topSortConfigArray($configArray);}- $sortedCollectors = array_keys($configArray);+//[补丁代码结束]+if (Mage::app()->useCache('config')) {Mage::app()->saveCache(serialize($sortedCollectors), $this->_collectorsCacheKey, array(Mage_Core_Model_Config::CACHE_TAG@@ -196,27 +246,6 @@}/**- * 使用 after/before 进行比较的回调- *- * @param 数组 $a- * @param 数组 $b- * @return int- */- 受保护的函数 _compareTotals($a, $b)- {- $aCode = $a['_code'];- $bCode = $b['_code'];- if (in_array($aCode, $b['after']) || in_array($bCode, $a['before'])) {- $res = -1;- } elseif (in_array($bCode, $a['after']) || in_array($aCode, $b['before'])) {- $res = 1;- } 别的 {- $res = 0;- }- 返回 $res;- }——-/*** 使用 sort_order 进行比较的回调** @param 数组 $a
编辑:还有另一个建议的更改(对于 Magento 2):https://github.com/magento/magento2/pull/49
In Magento there is a functionality where you can define the order of total calculation by specifing before and after which totals a total should be run.
I added a custom total and if I add the following lines to the config.xml, the sorting is wrong. Wrong means: tax_shipping comes before shipping.
This causes the tax for the shipping cost to be added twice.
But this violates the condition
tax_shipping
after: shipping
My guess: There must be some contradiction in the full set of rules. But how can I find it?
This is the only rule I add. Without this rule, tax_shipping is sorted after shipping.
<shippingprotectiontax>
<class>n98_shippingprotection/quote_address_total_shippingprotectionTax</class>
<after>subtotal,discount,shipping,tax</after>
<before>grand_total</before>
</shippingprotectiontax>
Below I paste the sorted array that is returned by the usort call in Mage_Sales_Model_Quote_Address_Total_Collector::_getSortedCollectorCodes()
For those who do not have a Magento installation, the code is like this:
/**
* uasort callback function
*
* @param array $a
* @param array $b
* @return int
*/
protected function _compareTotals($a, $b)
{
$aCode = $a['_code'];
$bCode = $b['_code'];
if (in_array($aCode, $b['after']) || in_array($bCode, $a['before'])) {
$res = -1;
} elseif (in_array($bCode, $a['after']) || in_array($aCode, $b['before'])) {
$res = 1;
} else {
$res = 0;
}
return $res;
}
protected function _getSortedCollectorCodes()
{
...
uasort($configArray, array($this, '_compareTotals'));
Mage::log('Sorted:');
// this produces the output below
$loginfo = "";
foreach($configArray as $code=>$data) {
$loginfo .= "$code
";
$loginfo .= "after: ".implode(',',$data['after'])."
";
$loginfo .= "before: ".implode(',',$data['before'])."
";
$loginfo .= "
";
}
Mage::log($loginfo);
...
Log output:
nominal
after:
before: subtotal,grand_total
subtotal
after: nominal
before: grand_total,shipping,freeshipping,tax_subtotal,discount,tax,weee,giftwrapping,cashondelivery,cashondelivery_tax,shippingprotection,shippingprotectiontax
freeshipping
after: subtotal,nominal
before: tax_subtotal,shipping,grand_total,tax,discount
tax_shipping
after: shipping,subtotal,freeshipping,tax_subtotal,nominal
before: tax,discount,grand_total,grand_total
giftwrapping
after: subtotal,nominal
before:
tax_subtotal
after: freeshipping,subtotal,subtotal,nominal
before: tax,discount,shipping,grand_total,weee,customerbalance,giftcardaccount,reward
weee
after: subtotal,tax_subtotal,nominal,freeshipping,subtotal,subtotal,nominal
before: tax,discount,grand_total,grand_total,tax
shipping
after: subtotal,freeshipping,tax_subtotal,nominal
before: grand_total,discount,tax_shipping,tax,cashondelivery,cashondelivery_tax,shippingprotection,shippingprotectiontax
discount
after: subtotal,shipping,nominal,freeshipping,tax_subtotal,tax_shipping,weee
before: grand_total,tax,customerbalance,giftcardaccount,reward,cashondelivery,cashondelivery_tax,shippingprotection,shippingprotectiontax
cashondelivery
after: subtotal,discount,shipping,nominal,subtotal,shipping,nominal,freeshipping,tax_subtotal,tax_shipping,weee,subtotal,freeshipping,tax_subtotal,nominal
before: tax,grand_total,grand_total,customerbalance,giftcardaccount,tax_giftwrapping,reward,customerbalance,giftcardaccount,reward
shippingprotection
after: subtotal,discount,shipping,nominal,subtotal,shipping,nominal,freeshipping,tax_subtotal,tax_shipping,weee,subtotal,freeshipping,tax_subtotal,nominal
before: tax,grand_total,grand_total,customerbalance,giftcardaccount,tax_giftwrapping,reward,cashondelivery_tax,customerbalance,giftcardaccount,reward
tax
after: subtotal,shipping,discount,tax_subtotal,freeshipping,tax_shipping,nominal,weee,cashondelivery,shippingprotection
before: grand_total,customerbalance,giftcardaccount,tax_giftwrapping,reward,cashondelivery_tax,shippingprotectiontax
shippingprotectiontax
after: subtotal,discount,shipping,tax,nominal,subtotal,shipping,nominal,freeshipping,tax_subtotal,tax_shipping,weee,subtotal,freeshipping,tax_subtotal,nominal,subtotal,shipping,discount,tax_subtotal,freeshipping,tax_shipping,nominal,weee,cashondelivery,shippingprotection
before: grand_total,customerbalance,giftcardaccount,reward
cashondelivery_tax
after: subtotal,discount,shipping,tax,nominal,subtotal,shipping,nominal,freeshipping,tax_subtotal,tax_shipping,weee,subtotal,freeshipping,tax_subtotal,nominal,subtotal,shipping,discount,tax_subtotal,freeshipping,tax_shipping,nominal,weee,cashondelivery
before: grand_total,customerbalance,giftcardaccount,reward
tax_giftwrapping
after: tax,subtotal,shipping,discount,tax_subtotal,freeshipping,tax_shipping,nominal,weee
before: grand_total,customerbalance,giftcardaccount
grand_total
after: subtotal,nominal,shipping,freeshipping,tax_subtotal,discount,tax,tax_giftwrapping,cashondelivery,cashondelivery_tax,shippingprotection,shippingprotectiontax
before: customerbalance,giftcardaccount,reward
reward
after: wee,discount,tax,tax_subtotal,grand_total,subtotal,shipping,nominal,freeshipping,tax_subtotal,tax_shipping,weee,subtotal,shipping,discount,tax_subtotal,freeshipping,tax_shipping,nominal,weee,freeshipping,subtotal,subtotal,nominal,subtotal,nominal,shipping,freeshipping,tax_subtotal,discount,tax,tax_giftwrapping
before: giftcardaccount,customerbalance,customerbalance
giftcardaccount
after: wee,discount,tax,tax_subtotal,grand_total,reward,subtotal,shipping,nominal,freeshipping,tax_shipping,weee
before: customerbalance
customerbalance
after: wee,discount,tax,tax_subtotal,grand_total,reward,giftcardaccount,subtotal,shipping,nominal,freeshipping,tax_shipping,weee
before:
EDIT:
After Vinai's answer I added more debug code
$fp = fopen('/tmp/dotfile','w');
fwrite($fp,"digraph TotalOrder
");
fwrite($fp,"{
");
foreach($configArray as $code=>$data) {
$_code = $data['_code'];
foreach($data['before'] as $beforeCode) {
fwrite($fp,"$beforeCode -> $_code;
");
}
foreach($data['after'] as $afterCode) {
fwrite($fp,"$_code -> $afterCode;
");
}
}
fwrite($fp,"}
");
fclose($fp);
And visualized it with graphviz: dot -Tpng dotfile > viz.png. That's the result of the first try. Called after the sorting.
EDIT2:
I think this is pretty useless.
So I made a visualization of the array before merging the after/before entries. (right after $configArray = $this->_modelsConfig;)
This is it without my shippingprotectiontax entry:
This is it with my shippingprotectiontax entry:
I do not see any clear contradictions.
EDIT3:
Config array just before uasort:
array (
'nominal' =>
array (
'class' => 'sales/quote_address_total_nominal',
'before' =>
array (
0 => 'subtotal',
1 => 'grand_total',
),
'renderer' => 'checkout/total_nominal',
'after' =>
array (
),
'_code' => 'nominal',
),
'subtotal' =>
array (
'class' => 'sales/quote_address_total_subtotal',
'after' =>
array (
0 => 'nominal',
),
'before' =>
array (
0 => 'grand_total',
1 => 'shipping',
2 => 'freeshipping',
3 => 'tax_subtotal',
4 => 'discount',
5 => 'tax',
6 => 'weee',
7 => 'giftwrapping',
8 => 'cashondelivery',
9 => 'cashondelivery_tax',
10 => 'shippingprotection',
11 => 'shippingprotectiontax',
),
'renderer' => 'tax/checkout_subtotal',
'admin_renderer' => 'adminhtml/sales_order_create_totals_subtotal',
'_code' => 'subtotal',
),
'shipping' =>
array (
'class' => 'sales/quote_address_total_shipping',
'after' =>
array (
0 => 'subtotal',
1 => 'freeshipping',
2 => 'tax_subtotal',
3 => 'nominal',
),
'before' =>
array (
0 => 'grand_total',
1 => 'discount',
2 => 'tax_shipping',
3 => 'tax',
4 => 'cashondelivery',
5 => 'cashondelivery_tax',
6 => 'shippingprotection',
7 => 'shippingprotectiontax',
),
'renderer' => 'tax/checkout_shipping',
'admin_renderer' => 'adminhtml/sales_order_create_totals_shipping',
'_code' => 'shipping',
),
'grand_total' =>
array (
'class' => 'sales/quote_address_total_grand',
'after' =>
array (
0 => 'subtotal',
1 => 'nominal',
2 => 'shipping',
3 => 'freeshipping',
4 => 'tax_subtotal',
5 => 'discount',
6 => 'tax',
7 => 'tax_giftwrapping',
8 => 'cashondelivery',
9 => 'cashondelivery_tax',
10 => 'shippingprotection',
11 => 'shippingprotectiontax',
),
'renderer' => 'tax/checkout_grandtotal',
'admin_renderer' => 'adminhtml/sales_order_create_totals_grandtotal',
'before' =>
array (
0 => 'customerbalance',
1 => 'giftcardaccount',
2 => 'reward',
),
'_code' => 'grand_total',
),
'freeshipping' =>
array (
'class' => 'salesrule/quote_freeshipping',
'after' =>
array (
0 => 'subtotal',
1 => 'nominal',
),
'before' =>
array (
0 => 'tax_subtotal',
1 => 'shipping',
2 => 'grand_total',
3 => 'tax',
4 => 'discount',
),
'_code' => 'freeshipping',
),
'discount' =>
array (
'class' => 'salesrule/quote_discount',
'after' =>
array (
0 => 'subtotal',
1 => 'shipping',
2 => 'nominal',
3 => 'freeshipping',
4 => 'tax_subtotal',
5 => 'tax_shipping',
6 => 'weee',
),
'before' =>
array (
0 => 'grand_total',
1 => 'tax',
2 => 'customerbalance',
3 => 'giftcardaccount',
4 => 'reward',
5 => 'cashondelivery',
6 => 'cashondelivery_tax',
7 => 'shippingprotection',
8 => 'shippingprotectiontax',
),
'renderer' => 'tax/checkout_discount',
'admin_renderer' => 'adminhtml/sales_order_create_totals_discount',
'_code' => 'discount',
),
'tax_subtotal' =>
array (
'class' => 'tax/sales_total_quote_subtotal',
'after' =>
array (
0 => 'freeshipping',
1 => 'subtotal',
2 => 'subtotal',
3 => 'nominal',
),
'before' =>
array (
0 => 'tax',
1 => 'discount',
2 => 'shipping',
3 => 'grand_total',
4 => 'weee',
5 => 'customerbalance',
6 => 'giftcardaccount',
7 => 'reward',
),
'_code' => 'tax_subtotal',
),
'tax_shipping' =>
array (
'class' => 'tax/sales_total_quote_shipping',
'after' =>
array (
0 => 'shipping',
1 => 'subtotal',
2 => 'freeshipping',
3 => 'tax_subtotal',
4 => 'nominal',
),
'before' =>
array (
0 => 'tax',
1 => 'discount',
2 => 'grand_total',
3 => 'grand_total',
),
'_code' => 'tax_shipping',
),
'tax' =>
array (
'class' => 'tax/sales_total_quote_tax',
'after' =>
array (
0 => 'subtotal',
1 => 'shipping',
2 => 'discount',
3 => 'tax_subtotal',
4 => 'freeshipping',
5 => 'tax_shipping',
6 => 'nominal',
7 => 'weee',
8 => 'cashondelivery',
9 => 'shippingprotection',
),
'before' =>
array (
0 => 'grand_total',
1 => 'customerbalance',
2 => 'giftcardaccount',
3 => 'tax_giftwrapping',
4 => 'reward',
5 => 'cashondelivery_tax',
6 => 'shippingprotectiontax',
),
'renderer' => 'tax/checkout_tax',
'admin_renderer' => 'adminhtml/sales_order_create_totals_tax',
'_code' => 'tax',
),
'weee' =>
array (
'class' => 'weee/total_quote_weee',
'after' =>
array (
0 => 'subtotal',
1 => 'tax_subtotal',
2 => 'nominal',
3 => 'freeshipping',
4 => 'subtotal',
5 => 'subtotal',
6 => 'nominal',
),
'before' =>
array (
0 => 'tax',
1 => 'discount',
2 => 'grand_total',
3 => 'grand_total',
4 => 'tax',
),
'_code' => 'weee',
),
'customerbalance' =>
array (
'class' => 'enterprise_customerbalance/total_quote_customerbalance',
'after' =>
array (
0 => 'wee',
1 => 'discount',
2 => 'tax',
3 => 'tax_subtotal',
4 => 'grand_total',
5 => 'reward',
6 => 'giftcardaccount',
7 => 'subtotal',
8 => 'shipping',
9 => 'nominal',
10 => 'freeshipping',
11 => 'tax_shipping',
12 => 'weee',
),
'renderer' => 'enterprise_customerbalance/checkout_total',
'before' =>
array (
),
'_code' => 'customerbalance',
),
'giftcardaccount' =>
array (
'class' => 'enterprise_giftcardaccount/total_quote_giftcardaccount',
'after' =>
array (
0 => 'wee',
1 => 'discount',
2 => 'tax',
3 => 'tax_subtotal',
4 => 'grand_total',
5 => 'reward',
6 => 'subtotal',
7 => 'shipping',
8 => 'nominal',
9 => 'freeshipping',
11 => 'tax_shipping',
12 => 'weee',
),
'before' =>
array (
0 => 'customerbalance',
),
'renderer' => 'enterprise_giftcardaccount/checkout_cart_total',
'_code' => 'giftcardaccount',
),
'giftwrapping' =>
array (
'class' => 'enterprise_giftwrapping/total_quote_giftwrapping',
'after' =>
array (
0 => 'subtotal',
1 => 'nominal',
),
'renderer' => 'enterprise_giftwrapping/checkout_totals',
'before' =>
array (
),
'_code' => 'giftwrapping',
),
'tax_giftwrapping' =>
array (
'class' => 'enterprise_giftwrapping/total_quote_tax_giftwrapping',
'after' =>
array (
0 => 'tax',
1 => 'subtotal',
2 => 'shipping',
3 => 'discount',
4 => 'tax_subtotal',
5 => 'freeshipping',
6 => 'tax_shipping',
7 => 'nominal',
8 => 'weee',
),
'before' =>
array (
0 => 'grand_total',
1 => 'customerbalance',
2 => 'giftcardaccount',
),
'_code' => 'tax_giftwrapping',
),
'reward' =>
array (
'class' => 'enterprise_reward/total_quote_reward',
'after' =>
array (
0 => 'wee',
1 => 'discount',
2 => 'tax',
3 => 'tax_subtotal',
4 => 'grand_total',
5 => 'subtotal',
6 => 'shipping',
7 => 'nominal',
8 => 'freeshipping',
9 => 'tax_subtotal',
10 => 'tax_shipping',
11 => 'weee',
12 => 'subtotal',
13 => 'shipping',
14 => 'discount',
15 => 'tax_subtotal',
16 => 'freeshipping',
17 => 'tax_shipping',
18 => 'nominal',
19 => 'weee',
20 => 'freeshipping',
21 => 'subtotal',
22 => 'subtotal',
23 => 'nominal',
24 => 'subtotal',
25 => 'nominal',
26 => 'shipping',
27 => 'freeshipping',
28 => 'tax_subtotal',
29 => 'discount',
30 => 'tax',
31 => 'tax_giftwrapping',
),
'before' =>
array (
0 => 'giftcardaccount',
1 => 'customerbalance',
2 => 'customerbalance',
),
'renderer' => 'enterprise_reward/checkout_total',
'_code' => 'reward',
),
'cashondelivery' =>
array (
'class' => 'cashondelivery/quote_total',
'after' =>
array (
0 => 'subtotal',
1 => 'discount',
2 => 'shipping',
3 => 'nominal',
4 => 'subtotal',
5 => 'shipping',
6 => 'nominal',
7 => 'freeshipping',
8 => 'tax_subtotal',
9 => 'tax_shipping',
10 => 'weee',
11 => 'subtotal',
12 => 'freeshipping',
13 => 'tax_subtotal',
14 => 'nominal',
),
'before' =>
array (
0 => 'tax',
1 => 'grand_total',
2 => 'grand_total',
3 => 'customerbalance',
4 => 'giftcardaccount',
5 => 'tax_giftwrapping',
6 => 'reward',
7 => 'customerbalance',
8 => 'giftcardaccount',
9 => 'reward',
),
'renderer' => 'cashondelivery/checkout_cod',
'admin_renderer' => 'cashondelivery/adminhtml_sales_order_create_totals_cod',
'_code' => 'cashondelivery',
),
'cashondelivery_tax' =>
array (
'class' => 'cashondelivery/quote_taxTotal',
'after' =>
array (
0 => 'subtotal',
1 => 'discount',
2 => 'shipping',
3 => 'tax',
4 => 'nominal',
5 => 'subtotal',
6 => 'shipping',
7 => 'nominal',
8 => 'freeshipping',
9 => 'tax_subtotal',
10 => 'tax_shipping',
11 => 'weee',
12 => 'subtotal',
13 => 'freeshipping',
14 => 'tax_subtotal',
15 => 'nominal',
16 => 'subtotal',
17 => 'shipping',
18 => 'discount',
19 => 'tax_subtotal',
20 => 'freeshipping',
21 => 'tax_shipping',
22 => 'nominal',
23 => 'weee',
24 => 'cashondelivery',
),
'before' =>
array (
0 => 'grand_total',
1 => 'customerbalance',
2 => 'giftcardaccount',
3 => 'reward',
),
'_code' => 'cashondelivery_tax',
),
'shippingprotection' =>
array (
'class' => 'n98_shippingprotection/quote_address_total_shippingprotection',
'after' =>
array (
0 => 'subtotal',
1 => 'discount',
2 => 'shipping',
3 => 'nominal',
4 => 'subtotal',
5 => 'shipping',
6 => 'nominal',
7 => 'freeshipping',
8 => 'tax_subtotal',
9 => 'tax_shipping',
10 => 'weee',
11 => 'subtotal',
12 => 'freeshipping',
13 => 'tax_subtotal',
14 => 'nominal',
),
'before' =>
array (
0 => 'tax',
1 => 'grand_total',
2 => 'grand_total',
3 => 'customerbalance',
4 => 'giftcardaccount',
5 => 'tax_giftwrapping',
6 => 'reward',
7 => 'cashondelivery_tax',
8 => 'customerbalance',
9 => 'giftcardaccount',
10 => 'reward',
),
'_code' => 'shippingprotection',
),
'shippingprotectiontax' =>
array (
'class' => 'n98_shippingprotection/quote_address_total_shippingprotectionTax',
'after' =>
array (
0 => 'subtotal',
1 => 'discount',
2 => 'shipping',
3 => 'tax',
4 => 'nominal',
5 => 'subtotal',
6 => 'shipping',
7 => 'nominal',
8 => 'freeshipping',
9 => 'tax_subtotal',
10 => 'tax_shipping',
11 => 'weee',
12 => 'subtotal',
13 => 'freeshipping',
14 => 'tax_subtotal',
15 => 'nominal',
16 => 'subtotal',
17 => 'shipping',
18 => 'discount',
19 => 'tax_subtotal',
20 => 'freeshipping',
21 => 'tax_shipping',
22 => 'nominal',
23 => 'weee',
24 => 'cashondelivery',
25 => 'shippingprotection',
),
'before' =>
array (
0 => 'grand_total',
1 => 'customerbalance',
2 => 'giftcardaccount',
3 => 'reward',
),
'_code' => 'shippingprotectiontax',
),
)
Update: Magento Bug Ticket: https://jira.magento.com/browse/MCACE-129
So finally, here is my patch for this issue.
It implements topological sorting as suggested by Vinai.
- Copy
app/code/core/Mage/Sales/Model/Config/Ordered.phptoapp/code/local/Mage/Sales/Model/Config/Ordered.php - Save the contents of the patch to a file
total-sorting.patchand callpatch -p0 app/code/local/Mage/Sales/Model/Config/Ordered.php
In case of upgrades make sure to re-apply these steps.
The patch is tested to work with Magento 1.7.0.2
--- app/code/core/Mage/Sales/Model/Config/Ordered.php 2012-08-14 14:19:50.306504947 +0200
+++ app/code/local/Mage/Sales/Model/Config/Ordered.php 2012-08-15 10:00:47.027003404 +0200
@@ -121,6 +121,78 @@
return $totalConfig;
}
+// [PATCHED CODE BEGIN]
+
+ /**
+ * Topological sort
+ *
+ * Copyright: http://www.calcatraz.com/blog/php-topological-sort-function-384
+ * And fix see comment on http://stackoverflow.com/questions/11953021/topological-sorting-in-php
+ *
+ * @param $nodeids Node Ids
+ * @param $edges Array of Edges. Each edge is specified as an array with two elements: The source and destination node of the edge
+ * @return array|null
+ */
+ function topological_sort($nodeids, $edges) {
+ $L = $S = $nodes = array();
+ foreach($nodeids as $id) {
+ $nodes[$id] = array('in'=>array(), 'out'=>array());
+ foreach($edges as $e) {
+ if ($id==$e[0]) { $nodes[$id]['out'][]=$e[1]; }
+ if ($id==$e[1]) { $nodes[$id]['in'][]=$e[0]; }
+ }
+ }
+ foreach ($nodes as $id=>$n) { if (empty($n['in'])) $S[]=$id; }
+ while ($id = array_shift($S)) {
+ if (!in_array($id, $L)) {
+ $L[] = $id;
+ foreach($nodes[$id]['out'] as $m) {
+ $nodes[$m]['in'] = array_diff($nodes[$m]['in'], array($id));
+ if (empty($nodes[$m]['in'])) { $S[] = $m; }
+ }
+ $nodes[$id]['out'] = array();
+ }
+ }
+ foreach($nodes as $n) {
+ if (!empty($n['in']) or !empty($n['out'])) {
+ return null; // not sortable as graph is cyclic
+ }
+ }
+ return $L;
+ }
+
+ /**
+ * Sort config array
+ *
+ * public to be easily accessable by test
+ *
+ * @param $configArray
+ * @return array
+ */
+ public function _topSortConfigArray($configArray)
+ {
+ $nodes = array_keys($configArray);
+ $edges = array();
+
+ foreach ($configArray as $code => $data) {
+ $_code = $data['_code'];
+ if (!isset($configArray[$_code])) continue;
+ foreach ($data['before'] as $beforeCode) {
+ if (!isset($configArray[$beforeCode])) continue;
+ $edges[] = array($_code, $beforeCode);
+ }
+
+ foreach ($data['after'] as $afterCode) {
+ if (!isset($configArray[$afterCode])) continue;
+ $edges[] = array($afterCode, $_code);
+ }
+ }
+ return $this->topological_sort($nodes, $edges);
+ }
+
+// [PATCHED CODE END]
+
+
/**
* Aggregate before/after information from all items and sort totals based on this data
*
@@ -138,38 +210,16 @@
// invoke simple sorting if the first element contains the "sort_order" key
reset($configArray);
$element = current($configArray);
+ // [PATCHED CODE BEGIN]
if (isset($element['sort_order'])) {
uasort($configArray, array($this, '_compareSortOrder'));
+ $sortedCollectors = array_keys($configArray);
+
} else {
- foreach ($configArray as $code => $data) {
- foreach ($data['before'] as $beforeCode) {
- if (!isset($configArray[$beforeCode])) {
- continue;
- }
- $configArray[$code]['before'] = array_unique(array_merge(
- $configArray[$code]['before'], $configArray[$beforeCode]['before']
- ));
- $configArray[$beforeCode]['after'] = array_merge(
- $configArray[$beforeCode]['after'], array($code), $data['after']
- );
- $configArray[$beforeCode]['after'] = array_unique($configArray[$beforeCode]['after']);
- }
- foreach ($data['after'] as $afterCode) {
- if (!isset($configArray[$afterCode])) {
- continue;
- }
- $configArray[$code]['after'] = array_unique(array_merge(
- $configArray[$code]['after'], $configArray[$afterCode]['after']
- ));
- $configArray[$afterCode]['before'] = array_merge(
- $configArray[$afterCode]['before'], array($code), $data['before']
- );
- $configArray[$afterCode]['before'] = array_unique($configArray[$afterCode]['before']);
- }
- }
- uasort($configArray, array($this, '_compareTotals'));
+ $sortedCollectors = $this->_topSortConfigArray($configArray);
}
- $sortedCollectors = array_keys($configArray);
+ // [PATCHED CODE END]
+
if (Mage::app()->useCache('config')) {
Mage::app()->saveCache(serialize($sortedCollectors), $this->_collectorsCacheKey, array(
Mage_Core_Model_Config::CACHE_TAG
@@ -196,27 +246,6 @@
}
/**
- * Callback that uses after/before for comparison
- *
- * @param array $a
- * @param array $b
- * @return int
- */
- protected function _compareTotals($a, $b)
- {
- $aCode = $a['_code'];
- $bCode = $b['_code'];
- if (in_array($aCode, $b['after']) || in_array($bCode, $a['before'])) {
- $res = -1;
- } elseif (in_array($bCode, $a['after']) || in_array($aCode, $b['before'])) {
- $res = 1;
- } else {
- $res = 0;
- }
- return $res;
- }
-
- /**
* Callback that uses sort_order for comparison
*
* @param array $a
EDIT: There is also another suggested change (for Magento 2): https://github.com/magento/magento2/pull/49
这篇关于排序算法:Magento 结帐总额排序错误导致运费计算错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!
本文标题为:排序算法:Magento 结帐总额排序错误导致运费计算
基础教程推荐
- 主题化 Drupal 7 的 Ubercart “/cart"页 2021-01-01
- 使用 scandir() 在目录中查找文件夹 (PHP) 2022-01-01
- php 7.4 在写入变量中的 Twig 问题 2022-01-01
- php中的PDF导出 2022-01-01
- Web 服务器如何处理请求? 2021-01-01
- PHPUnit 的 Selenium 2 文档到底在哪里? 2022-01-01
- 将变量从树枝传递给 js 2022-01-01
- php中的foreach复选框POST 2021-01-01
- Yii2 - 在运行时设置邮件传输参数 2022-01-01
- 如何在数学上评估像“2-1"这样的字符串?产生“1"? 2022-01-01
