Version Notes
Initial release.
Download this release
Release Info
Developer | Severin Klingler |
Extension | store_dependency |
Version | 1.0.0 |
Comparing to | |
See all releases |
Version 1.0.0
- app/code/local/Customweb/Dependency/Helper/Config.php +332 -0
- app/code/local/Customweb/Dependency/Helper/Data.php +65 -0
- app/code/local/Customweb/Dependency/Helper/Element.php +223 -0
- app/code/local/Customweb/Dependency/Model/Category.php +16 -0
- app/code/local/Customweb/Dependency/Model/Config.php +17 -0
- app/code/local/Customweb/Dependency/Model/ConfigObserver.php +364 -0
- app/code/local/Customweb/Dependency/Model/Mysql4/Category.php +14 -0
- app/code/local/Customweb/Dependency/Model/Mysql4/Category/Collection.php +14 -0
- app/code/local/Customweb/Dependency/Model/Mysql4/Config.php +15 -0
- app/code/local/Customweb/Dependency/Model/Mysql4/Config/Collection.php +14 -0
- app/code/local/Customweb/Dependency/Model/Mysql4/Product.php +15 -0
- app/code/local/Customweb/Dependency/Model/Mysql4/Product/Collection.php +15 -0
- app/code/local/Customweb/Dependency/Model/Product.php +17 -0
- app/code/local/Customweb/Dependency/Model/Resource/Setup.php +12 -0
- app/code/local/Customweb/Dependency/controllers/TestController.php +12 -0
- app/code/local/Customweb/Dependency/etc/config.xml +108 -0
- app/code/local/Customweb/Dependency/sql/dependency_setup/mysql4-install-1.0.0.php +45 -0
- app/code/local/Mage/Core/Model/Config.php +1625 -0
- app/code/local/Varien/Data/Form/Element/Abstract.php +314 -0
- app/design/adminhtml/default/default/layout/dependency.xml +11 -0
- app/etc/modules/Customweb_Dependency.xml +11 -0
- js/customweb/dependency.js +116 -0
- package.xml +27 -0
- skin/adminhtml/base/default/css/dependency.css +76 -0
app/code/local/Customweb/Dependency/Helper/Config.php
ADDED
@@ -0,0 +1,332 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
::[Header]::
|
4 |
+
* @category Local
|
5 |
+
* @package Customweb_Dependency
|
6 |
+
* @link http://www.customweb.ch
|
7 |
+
*/
|
8 |
+
|
9 |
+
class Customweb_Dependency_Helper_Config extends Mage_Core_Helper_Abstract
|
10 |
+
{
|
11 |
+
/**
|
12 |
+
* Get all edges as a adjacency list where result[a] contains
|
13 |
+
* all the nodes that depend on a
|
14 |
+
*
|
15 |
+
* @param string $path
|
16 |
+
* @return array
|
17 |
+
*/
|
18 |
+
public function getDependentConfigEdges($path)
|
19 |
+
{
|
20 |
+
$edges = array();
|
21 |
+
$attributeCollection = Mage::getModel('dependency/config')->getCollection();
|
22 |
+
$attributeCollection->addFieldToFilter('path', $path);
|
23 |
+
|
24 |
+
foreach($attributeCollection as $att)
|
25 |
+
{
|
26 |
+
$edges[$att->getDependsOnScope() . '_' . $att->getDependsOnScopeId()][] = $att->getScope() . '_' . $att->getScopeId();
|
27 |
+
}
|
28 |
+
return $edges;
|
29 |
+
}
|
30 |
+
|
31 |
+
|
32 |
+
/**
|
33 |
+
* Get all edges as a adjacency list where result[a] contains
|
34 |
+
* all the nodes that a depends on
|
35 |
+
*
|
36 |
+
* @param string $path
|
37 |
+
* @return array
|
38 |
+
*/
|
39 |
+
public function getDependeeConfigEdges($path)
|
40 |
+
{
|
41 |
+
$edges = array();
|
42 |
+
$attributeCollection = Mage::getModel('dependency/config')->getCollection();
|
43 |
+
$attributeCollection->addFieldToFilter('path', $path);
|
44 |
+
|
45 |
+
foreach($attributeCollection as $att)
|
46 |
+
{
|
47 |
+
$edges[$att->getScope() . '_' . $att->getScopeId()] = $att->getDependsOnScope() . '_' . $att->getDependsOnScopeId();
|
48 |
+
}
|
49 |
+
return $edges;
|
50 |
+
}
|
51 |
+
|
52 |
+
/**
|
53 |
+
* Returns the dependency of an configuration entry.
|
54 |
+
*
|
55 |
+
* @param string $path
|
56 |
+
* @param string $scope
|
57 |
+
* @param string $scope_id
|
58 |
+
* @return Customweb_Dependency_Model_Config
|
59 |
+
*/
|
60 |
+
public function getConfigDependency($path,$scope,$scope_id)
|
61 |
+
{
|
62 |
+
$configCollection = Mage::getModel('dependency/config')->getCollection();
|
63 |
+
$configCollection->addFieldToFilter('path', $path)
|
64 |
+
->addFieldToFilter('scope', $scope)
|
65 |
+
->addFieldToFilter('scope_id', intval($scope_id));
|
66 |
+
$configDependency = $configCollection->getFirstItem();
|
67 |
+
if(!$configDependency->getId())
|
68 |
+
{
|
69 |
+
$configDependency = Mage::getModel('dependency/config');
|
70 |
+
$configDependency->setPath($path);
|
71 |
+
$configDependency->setScope($scope);
|
72 |
+
$configDependency->setScopeId($scope_id);
|
73 |
+
}
|
74 |
+
return $configDependency;
|
75 |
+
}
|
76 |
+
|
77 |
+
/**
|
78 |
+
* Translates between website and store names and corresponding
|
79 |
+
* IDs.
|
80 |
+
*
|
81 |
+
* @param string $websiteName
|
82 |
+
* @param string $storeName
|
83 |
+
*/
|
84 |
+
public function getConfigId($websiteName, $storeName)
|
85 |
+
{
|
86 |
+
$this->initTranslateArrays();
|
87 |
+
|
88 |
+
if($storeName == null)
|
89 |
+
{
|
90 |
+
return $this->websiteNameToCode[$websiteName];
|
91 |
+
}
|
92 |
+
else
|
93 |
+
{
|
94 |
+
return $this->storeNameToCode[$storeName];
|
95 |
+
}
|
96 |
+
|
97 |
+
}
|
98 |
+
|
99 |
+
/**
|
100 |
+
* Translates the select identifier value to a pair of
|
101 |
+
* scope and scopeId.
|
102 |
+
*
|
103 |
+
* This code is based on Mage_Adminhtml_Model_Config_Data::_getScope()
|
104 |
+
*
|
105 |
+
* @param string $string
|
106 |
+
* @return array
|
107 |
+
*/
|
108 |
+
public function getScope($string)
|
109 |
+
{
|
110 |
+
try {
|
111 |
+
$parts = explode('_',$string,2);
|
112 |
+
} catch (Exception $e) {
|
113 |
+
throw new Exception(print_r($string,true) . $e->getMessage());
|
114 |
+
}
|
115 |
+
|
116 |
+
if(count($parts) == 2)
|
117 |
+
{
|
118 |
+
$scope = $parts[0] . 's';
|
119 |
+
$scopeCode = $parts[1];
|
120 |
+
$scopeId = (int)Mage::getConfig()->getNode($scope . '/' . $scopeCode . '/system/' . $parts[0] . '/id');
|
121 |
+
}
|
122 |
+
else
|
123 |
+
{
|
124 |
+
$scope = 'default';
|
125 |
+
$scopeId = 0;
|
126 |
+
}
|
127 |
+
return array('scope' => $scope, 'scope_id' => $scopeId);
|
128 |
+
}
|
129 |
+
|
130 |
+
/**
|
131 |
+
* Splits a website/store identifier string of the form "stores_2" into an array.
|
132 |
+
*
|
133 |
+
* @param string $string website/store identifier
|
134 |
+
* @return array
|
135 |
+
*/
|
136 |
+
public function splitScopeString($string)
|
137 |
+
{
|
138 |
+
$parts = explode('_',$string,2);
|
139 |
+
return array('scope' => $parts[0], 'scope_id' => $parts[1]);
|
140 |
+
}
|
141 |
+
|
142 |
+
|
143 |
+
/**
|
144 |
+
* Create the mappings between website/store identifier strings and the website/store
|
145 |
+
* codes.
|
146 |
+
*/
|
147 |
+
private function initTranslateArrays()
|
148 |
+
{
|
149 |
+
$storeModel = Mage::getSingleton('adminhtml/system_store');
|
150 |
+
|
151 |
+
if($this->websiteNameToCode == null)
|
152 |
+
{
|
153 |
+
$this->websiteNameToCode = array();
|
154 |
+
foreach ($storeModel->getWebsiteCollection() as $website)
|
155 |
+
{
|
156 |
+
$this->websiteNameToCode[$website->getCode()] = $this->getScope('website_' . $website->getCode());
|
157 |
+
$scope = $this->getScope('website_' . $website->getCode());
|
158 |
+
$this->websiteCodeToName[$scope['scope_id']] = $website->getCode();
|
159 |
+
}
|
160 |
+
}
|
161 |
+
if($this->storeNameToCode == null)
|
162 |
+
{
|
163 |
+
$this->storeNameToCode = array();
|
164 |
+
foreach ($storeModel->getStoreCollection() as $store)
|
165 |
+
{
|
166 |
+
$this->storeNameToCode[$store->getCode()] = $this->getScope('store_' . $store->getCode());
|
167 |
+
$scope = $this->getScope('store_' . $store->getCode());
|
168 |
+
$this->storeCodeToName[$scope['scope_id']] = $store->getCode();
|
169 |
+
}
|
170 |
+
}
|
171 |
+
}
|
172 |
+
|
173 |
+
/**
|
174 |
+
* Checks for loops if we add the given new dependency.
|
175 |
+
*
|
176 |
+
* @param mixed $dependency
|
177 |
+
* @param string $type
|
178 |
+
* @return boolean True if the dependency graph does not contain a cylce starting at the specified node
|
179 |
+
*/
|
180 |
+
public function doesYieldNoLoop($dependency){
|
181 |
+
|
182 |
+
$edgesToDependent = $this->getDependentConfigEdges(
|
183 |
+
$dependency->getPath());
|
184 |
+
|
185 |
+
$node = $dependency->getScope() . '_' . $dependency->getScopeId();
|
186 |
+
$newDependency = $dependency->getDependsOnScope() . '_' . $dependency->getDependsOnScopeId();
|
187 |
+
|
188 |
+
|
189 |
+
|
190 |
+
// Only add the dependency if it does not already exist.
|
191 |
+
if(isset($edgesToDependent[$newDependency]))
|
192 |
+
{
|
193 |
+
if(!in_array($node,
|
194 |
+
$edgesToDependent[$newDependency]))
|
195 |
+
{
|
196 |
+
$edgesToDependent[$newDependency][] = $node;
|
197 |
+
}
|
198 |
+
}
|
199 |
+
else
|
200 |
+
{
|
201 |
+
$edgesToDependent[$newDependency] = array($node);
|
202 |
+
}
|
203 |
+
|
204 |
+
return Mage::helper('dependency')->dfs($node,$edgesToDependent);
|
205 |
+
}
|
206 |
+
|
207 |
+
/**
|
208 |
+
* Returns the path of the config value extracted from the html id and the element name
|
209 |
+
* of the respective form element.
|
210 |
+
*
|
211 |
+
* @param string $htmlId
|
212 |
+
* @param string $elementName
|
213 |
+
* @return string
|
214 |
+
*/
|
215 |
+
public function getPath($htmlId, $elementName)
|
216 |
+
{
|
217 |
+
preg_match('/groups\[(.*)\]\[fields\]\[(.*)\]\[value/i', $elementName, $m);
|
218 |
+
|
219 |
+
$group = $m[1];
|
220 |
+
$entry = $m[2];
|
221 |
+
preg_match('/(.*)\_' . $m[1] . '/', $htmlId, $m);
|
222 |
+
$base = $m[1];
|
223 |
+
|
224 |
+
return $this->createPath($base,$group,$entry);
|
225 |
+
}
|
226 |
+
|
227 |
+
/**
|
228 |
+
* Delete the dependency from the config value given, if any.
|
229 |
+
*
|
230 |
+
* @param string $path
|
231 |
+
* @param string $scope
|
232 |
+
* @param string $scopeId
|
233 |
+
*/
|
234 |
+
public function deleteDependenyOf($path, $scope, $scopeId)
|
235 |
+
{
|
236 |
+
$dependency = $this->getConfigDependency($path,$scope,$scopeId);
|
237 |
+
if($dependency->getId())
|
238 |
+
{
|
239 |
+
$dependency->delete();
|
240 |
+
}
|
241 |
+
}
|
242 |
+
|
243 |
+
/**
|
244 |
+
* Creates the correct path to the config value. Especially it handles custom
|
245 |
+
* defined field paths.
|
246 |
+
* Code taken from Mage_Adminhtml_Model_Config_Data::save()
|
247 |
+
*
|
248 |
+
* @param string $section
|
249 |
+
* @param string $group
|
250 |
+
* @param string $name
|
251 |
+
* @return string
|
252 |
+
*/
|
253 |
+
public function createPath($section,$group,$name)
|
254 |
+
{
|
255 |
+
$path = $section . '/' . $group . '/' . $name;
|
256 |
+
$fieldConfig = $this->getSections()->descend($section.'/groups/'.$group.'/fields/'.$name);
|
257 |
+
|
258 |
+
/**
|
259 |
+
* Look for custom defined field path
|
260 |
+
*/
|
261 |
+
if (is_object($fieldConfig)) {
|
262 |
+
$configPath = (string)$fieldConfig->config_path;
|
263 |
+
if (!empty($configPath) && strrpos($configPath, '/') > 0) {
|
264 |
+
$path = $configPath;
|
265 |
+
}
|
266 |
+
}
|
267 |
+
return $path;
|
268 |
+
}
|
269 |
+
|
270 |
+
/**
|
271 |
+
* Returns the store name based on the store code
|
272 |
+
* @param int $code
|
273 |
+
* @return string
|
274 |
+
*/
|
275 |
+
public function getStoreName($code)
|
276 |
+
{
|
277 |
+
$this->initTranslateArrays();
|
278 |
+
return $this->storeCodeToName[$code];
|
279 |
+
}
|
280 |
+
|
281 |
+
/**
|
282 |
+
* Returns the website name based on the website code.
|
283 |
+
* @param int $code
|
284 |
+
* @return string
|
285 |
+
*/
|
286 |
+
public function getWebsiteName($code)
|
287 |
+
{
|
288 |
+
$this->initTranslateArrays();
|
289 |
+
return $this->websiteCodeToName[$code];
|
290 |
+
}
|
291 |
+
|
292 |
+
/**
|
293 |
+
* Makes sure the dependency is valid. This does not check for loops in the
|
294 |
+
* dependency graphs it only makes sure no dependencies from website level to store level
|
295 |
+
* are possible.
|
296 |
+
*
|
297 |
+
* @param Customweb_Dependency_Model_Config $dependency
|
298 |
+
* @param string $throwException True(default) the method throws an exception, false the method returns false on invalid dependencies.
|
299 |
+
* @throws Exception
|
300 |
+
* @return boolean
|
301 |
+
*/
|
302 |
+
public function validateDependency(Customweb_Dependency_Model_Config $dependency, $throwException=true)
|
303 |
+
{
|
304 |
+
if($dependency->getScope() == 'websites' && $dependency->getDependsOnScope() == 'stores')
|
305 |
+
{
|
306 |
+
if($throwException)
|
307 |
+
{
|
308 |
+
throw new Exception(Mage::helper('dependency')->__("A website cannot depend on a store view."));
|
309 |
+
}
|
310 |
+
return false;
|
311 |
+
}
|
312 |
+
return true;
|
313 |
+
}
|
314 |
+
|
315 |
+
/**
|
316 |
+
* Returns all configuration sections.
|
317 |
+
*/
|
318 |
+
private function getSections()
|
319 |
+
{
|
320 |
+
if($this->sections == null){
|
321 |
+
$this->sections = Mage::getModel('adminhtml/config')->getSections();
|
322 |
+
}
|
323 |
+
return $this->sections;
|
324 |
+
}
|
325 |
+
|
326 |
+
protected $storeNameToCode = null;
|
327 |
+
protected $websiteNameToCode = null;
|
328 |
+
protected $storeCodeToName = null;
|
329 |
+
protected $websiteCodeToName = null;
|
330 |
+
protected $sections = null;
|
331 |
+
|
332 |
+
}
|
app/code/local/Customweb/Dependency/Helper/Data.php
ADDED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
::[Header]::
|
4 |
+
* @category Local
|
5 |
+
* @package Customweb_Dependency
|
6 |
+
* @link http://www.customweb.ch
|
7 |
+
*/
|
8 |
+
|
9 |
+
class Customweb_Dependency_Helper_Data extends Mage_Core_Helper_Abstract
|
10 |
+
{
|
11 |
+
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Traverses the graph provided by $edges starting at node $node and looks for
|
15 |
+
* cycles.
|
16 |
+
*
|
17 |
+
* @param int $node
|
18 |
+
* @param array $edges
|
19 |
+
* @return boolean True if graph contains no cylces starting at $node
|
20 |
+
*/
|
21 |
+
public function dfs($node,$edges)
|
22 |
+
{
|
23 |
+
static $visited = array();
|
24 |
+
$noLoop = true;
|
25 |
+
if(array_key_exists($node,$visited) && $visited[$node] == 1)
|
26 |
+
{
|
27 |
+
return false;
|
28 |
+
}
|
29 |
+
else
|
30 |
+
{
|
31 |
+
$visited[$node] = 1;
|
32 |
+
|
33 |
+
if(isset($edges[$node]))
|
34 |
+
{
|
35 |
+
foreach($edges[$node] as $nextNode)
|
36 |
+
{
|
37 |
+
$noLoop = $noLoop && $this->dfs($nextNode,$edges);
|
38 |
+
}
|
39 |
+
}
|
40 |
+
}
|
41 |
+
|
42 |
+
$visited[$node] = 0;
|
43 |
+
return $noLoop;
|
44 |
+
}
|
45 |
+
|
46 |
+
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Returns an array of all store ids.
|
50 |
+
*
|
51 |
+
* @return array List of store ids.
|
52 |
+
*/
|
53 |
+
public function getAllStoreIds()
|
54 |
+
{
|
55 |
+
return array_keys(Mage::app()->getStores());
|
56 |
+
}
|
57 |
+
|
58 |
+
|
59 |
+
|
60 |
+
/**
|
61 |
+
* Stores the mapping from type names to model names.
|
62 |
+
* @var array
|
63 |
+
*/
|
64 |
+
protected $typeToModel = null;
|
65 |
+
}
|
app/code/local/Customweb/Dependency/Helper/Element.php
ADDED
@@ -0,0 +1,223 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
::[Header]::
|
4 |
+
* @category Local
|
5 |
+
* @package Customweb_Dependency
|
6 |
+
* @link http://www.customweb.ch
|
7 |
+
*/
|
8 |
+
|
9 |
+
class Customweb_Dependency_Helper_Element extends Mage_Core_Helper_Abstract
|
10 |
+
{
|
11 |
+
|
12 |
+
private $name = null;
|
13 |
+
private $htmlId = null;
|
14 |
+
private $elementId = null;
|
15 |
+
private $params = null;
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Returns a HTML suffix that is added to every element of applicable admin forms.
|
19 |
+
* The suffix contains a select field that allows the user to select on what store view/website
|
20 |
+
* the element is dependent.
|
21 |
+
*
|
22 |
+
* @param string $name Name of the element
|
23 |
+
* @param string $htmlId HTML ID of the element
|
24 |
+
* @param string $elementId ID of the element
|
25 |
+
* @param array $params HTTP Request parameters
|
26 |
+
* @return string
|
27 |
+
*/
|
28 |
+
public function getElementSuffix($name,$htmlId,$elementId,$params)
|
29 |
+
{
|
30 |
+
$this->name = $name;
|
31 |
+
$this->htmlId = $htmlId;
|
32 |
+
$this->elementId = $elementId;
|
33 |
+
$this->params = $params;
|
34 |
+
$suffix = "";
|
35 |
+
|
36 |
+
|
37 |
+
|
38 |
+
|
39 |
+
$suffix = $this->getSuffixForConfigElement();
|
40 |
+
|
41 |
+
return $suffix;
|
42 |
+
}
|
43 |
+
|
44 |
+
|
45 |
+
|
46 |
+
|
47 |
+
/**
|
48 |
+
* Returns the select field for configuration elements.
|
49 |
+
*
|
50 |
+
* @return string HTML suffix
|
51 |
+
*/
|
52 |
+
private function getSuffixForConfigElement()
|
53 |
+
{
|
54 |
+
$suffix = "";
|
55 |
+
|
56 |
+
if(isset($this->params['website']))
|
57 |
+
{
|
58 |
+
$websiteName = $this->params['website'];
|
59 |
+
$storeName = (isset($this->params['store']) ? $this->params['store'] : null);
|
60 |
+
$helper = Mage::helper('dependency/config');
|
61 |
+
|
62 |
+
if(preg_match('/groups\[(.*)\]\[fields\]\[(.*)\]\[value/i',$this->name))
|
63 |
+
{
|
64 |
+
$dependency = Mage::getModel('dependency/config');
|
65 |
+
$path = $helper->getPath($this->htmlId, $this->name);
|
66 |
+
$scope = $helper->getConfigId($websiteName,$storeName);
|
67 |
+
$dependsOn = $helper->getConfigDependency($path,$scope['scope'],$scope['scope_id']);
|
68 |
+
|
69 |
+
$type = 'config';
|
70 |
+
|
71 |
+
$selectName = preg_replace('/^(.*)\[value\]/i', '$1[source]' ,$this->name);
|
72 |
+
$dependencyName = preg_replace('/^(.*)\[value\]/i', '$1[dependency]' ,$this->name);
|
73 |
+
|
74 |
+
$selectHtml = $this->createScopeDependencySelect($selectName,$dependsOn);
|
75 |
+
|
76 |
+
$suffix = '<input type="hidden" name="' . $dependencyName . '" value="1" />';
|
77 |
+
$suffix .= '<div class="dependency-box"><div class="dependency-select">' . $selectHtml . '</div></div>';
|
78 |
+
}
|
79 |
+
}
|
80 |
+
return $suffix;
|
81 |
+
}
|
82 |
+
|
83 |
+
/**
|
84 |
+
* Creates a select element that looks like the standard magento store switcher select
|
85 |
+
* for configurations.
|
86 |
+
* This code is partly taken from template/system/config/switcher.phtml
|
87 |
+
*/
|
88 |
+
private function createScopeDependencySelect($name,$selected)
|
89 |
+
{
|
90 |
+
$dependsOnWebsite = false;
|
91 |
+
$dependsOnStore = false;
|
92 |
+
$scope = $selected->getDependsOnScope();
|
93 |
+
$helper = Mage::helper('dependency/config');
|
94 |
+
|
95 |
+
|
96 |
+
|
97 |
+
if($scope == 'websites')
|
98 |
+
{
|
99 |
+
$dependsOnWebsite = $helper->getWebsiteName($selected->getDependsOnScopeId());
|
100 |
+
}
|
101 |
+
elseif ($scope == 'stores')
|
102 |
+
{
|
103 |
+
$dependsOnStore = $helper->getStoreName($selected->getDependsOnScopeId());
|
104 |
+
}
|
105 |
+
|
106 |
+
$html = '<select id="store_switcher" class="config-dependency-selector" dependencyfor="' . $this->htmlId .'" name="' . $name . '" onchange="' . $this->getUpdateJsString() .'" >';
|
107 |
+
foreach ($this->getStoreSelectOptions($dependsOnStore,$dependsOnWebsite) as $_value => $_option)
|
108 |
+
{
|
109 |
+
if (isset($_option['is_group']))
|
110 |
+
{
|
111 |
+
if ($_option['is_close'])
|
112 |
+
{
|
113 |
+
$html .= '</optgroup>';
|
114 |
+
}
|
115 |
+
else
|
116 |
+
{
|
117 |
+
$html .= '<optgroup label="' . $this->escapeHtml($_option['label']) . '" style="' . $_option['style'] . '">';
|
118 |
+
}
|
119 |
+
continue;
|
120 |
+
}
|
121 |
+
// Check for dependency
|
122 |
+
$scope = $helper->getScope($_value);
|
123 |
+
$selected->setDependsOnScope($scope['scope']);
|
124 |
+
$selected->setDependsOnScopeId($scope['scope_id']);
|
125 |
+
$optionClass = 'dependency-check-failed';
|
126 |
+
if($helper->validateDependency($selected,false))
|
127 |
+
{
|
128 |
+
$optionClass = 'dependency-loop';
|
129 |
+
if(Mage::helper('dependency/config')->doesYieldNoLoop($selected))
|
130 |
+
{
|
131 |
+
$optionClass = 'dependency-ok';
|
132 |
+
}
|
133 |
+
}
|
134 |
+
$html .= '<option value="' . $this->escapeHtml($_value) . '" url="' . $_option['url'] . '" ' . ($_option['selected'] ? 'selected="selected"' : '') . ' class="' . $optionClass . '" ' . ' style="' . $_option['style'] . '">';
|
135 |
+
$html .= $this->escapeHtml($_option['label']) . '</option>';
|
136 |
+
}
|
137 |
+
$html .= '</select>';
|
138 |
+
return $html;
|
139 |
+
}
|
140 |
+
|
141 |
+
/**
|
142 |
+
* Taken from Mage_Adminhtml_Block_System_Config_Switcher
|
143 |
+
*/
|
144 |
+
private function getStoreSelectOptions($curStore,$curWebsite)
|
145 |
+
{
|
146 |
+
$section = $this->params['section'];
|
147 |
+
|
148 |
+
$storeModel = Mage::getSingleton('adminhtml/system_store');
|
149 |
+
/* @var $storeModel Mage_Adminhtml_Model_System_Store */
|
150 |
+
|
151 |
+
$url = Mage::getModel('adminhtml/url');
|
152 |
+
|
153 |
+
$options = array();
|
154 |
+
$options['default'] = array(
|
155 |
+
'label' => Mage::helper('dependency')->__('No dependency'),
|
156 |
+
'url' => $url->getUrl('*/*/*', array('section'=>$section)),
|
157 |
+
'selected' => !$curWebsite && !$curStore,
|
158 |
+
'style' => 'background:#ccc; font-weight:bold;',
|
159 |
+
);
|
160 |
+
|
161 |
+
foreach ($storeModel->getWebsiteCollection() as $website) {
|
162 |
+
$websiteShow = false;
|
163 |
+
foreach ($storeModel->getGroupCollection() as $group) {
|
164 |
+
if ($group->getWebsiteId() != $website->getId()) {
|
165 |
+
continue;
|
166 |
+
}
|
167 |
+
$groupShow = false;
|
168 |
+
foreach ($storeModel->getStoreCollection() as $store) {
|
169 |
+
if ($store->getGroupId() != $group->getId()) {
|
170 |
+
continue;
|
171 |
+
}
|
172 |
+
if (!$websiteShow) {
|
173 |
+
$websiteShow = true;
|
174 |
+
$options['website_' . $website->getCode()] = array(
|
175 |
+
'label' => $website->getName(),
|
176 |
+
'url' => $url->getUrl('*/*/*', array('section'=>$section, 'website'=>$website->getCode())),
|
177 |
+
'selected' => !$curStore && $curWebsite == $website->getCode(),
|
178 |
+
'style' => 'padding-left:16px; background:#DDD; font-weight:bold;',
|
179 |
+
);
|
180 |
+
}
|
181 |
+
if (!$groupShow) {
|
182 |
+
$groupShow = true;
|
183 |
+
$options['group_' . $group->getId() . '_open'] = array(
|
184 |
+
'is_group' => true,
|
185 |
+
'is_close' => false,
|
186 |
+
'label' => $group->getName(),
|
187 |
+
'style' => 'padding-left:32px;'
|
188 |
+
);
|
189 |
+
}
|
190 |
+
$options['store_' . $store->getCode()] = array(
|
191 |
+
'label' => $store->getName(),
|
192 |
+
'url' => $url->getUrl('*/*/*', array('section'=>$section, 'website'=>$website->getCode(), 'store'=>$store->getCode())),
|
193 |
+
'selected' => $curStore == $store->getCode(),
|
194 |
+
'style' => '',
|
195 |
+
);
|
196 |
+
}
|
197 |
+
if ($groupShow) {
|
198 |
+
$options['group_' . $group->getId() . '_close'] = array(
|
199 |
+
'is_group' => true,
|
200 |
+
'is_close' => true,
|
201 |
+
);
|
202 |
+
}
|
203 |
+
}
|
204 |
+
}
|
205 |
+
|
206 |
+
return $options;
|
207 |
+
}
|
208 |
+
|
209 |
+
private function getUpdateJsString()
|
210 |
+
{
|
211 |
+
$loopError = Mage::helper('dependency')->__("Can not depend on this store view. Loop detected.");
|
212 |
+
$dependencyCheckError = Mage::helper('dependency')->__("A website cannot depend on a store view.");
|
213 |
+
|
214 |
+
return 'updateDependencySelect(this,\'' . $this->htmlId .'\',false,\'' . $loopError . '\',\'' . $dependencyCheckError . '\')';
|
215 |
+
}
|
216 |
+
|
217 |
+
|
218 |
+
|
219 |
+
|
220 |
+
|
221 |
+
|
222 |
+
|
223 |
+
}
|
app/code/local/Customweb/Dependency/Model/Category.php
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
::[Header]::
|
4 |
+
* @category Local
|
5 |
+
* @package Customweb_Dependency
|
6 |
+
* @link http://www.customweb.ch
|
7 |
+
*/
|
8 |
+
|
9 |
+
class Customweb_Dependency_Model_Category extends Mage_Core_Model_Abstract
|
10 |
+
{
|
11 |
+
protected function _construct()
|
12 |
+
{
|
13 |
+
$this->_init('dependency/category');
|
14 |
+
}
|
15 |
+
|
16 |
+
}
|
app/code/local/Customweb/Dependency/Model/Config.php
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
::[Header]::
|
5 |
+
* @category Local
|
6 |
+
* @package Customweb_Dependency
|
7 |
+
* @link http://www.customweb.ch
|
8 |
+
*/
|
9 |
+
|
10 |
+
class Customweb_Dependency_Model_Config extends Mage_Core_Model_Abstract
|
11 |
+
{
|
12 |
+
protected function _construct()
|
13 |
+
{
|
14 |
+
$this->_init('dependency/config');
|
15 |
+
}
|
16 |
+
|
17 |
+
}
|
app/code/local/Customweb/Dependency/Model/ConfigObserver.php
ADDED
@@ -0,0 +1,364 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
::[Header]::
|
4 |
+
* @category Local
|
5 |
+
* @package Customweb_Dependency
|
6 |
+
* @link http://www.customweb.ch
|
7 |
+
*/
|
8 |
+
|
9 |
+
class Customweb_Dependency_Model_ConfigObserver
|
10 |
+
{
|
11 |
+
/**
|
12 |
+
* The method is registered to the event 'model_config_data_save_before'. It updates
|
13 |
+
* the dependency graph.
|
14 |
+
*
|
15 |
+
* @param Varien_Event_Observer $observer
|
16 |
+
*/
|
17 |
+
public function configSaveBefore(Varien_Event_Observer $observer)
|
18 |
+
{
|
19 |
+
$event = $observer->getEvent();
|
20 |
+
$config = $event->getObject();
|
21 |
+
|
22 |
+
$section = $config->getSection();
|
23 |
+
$website = $config->getWebsite();
|
24 |
+
$store = $config->getStore();
|
25 |
+
$groups = $config->getGroups();
|
26 |
+
$scope = $config->getScope();
|
27 |
+
$scopeId = $config->getScopeId();
|
28 |
+
$this->handleConfigs = array();
|
29 |
+
|
30 |
+
$write = Mage::getSingleton('core/resource')->getConnection('core_write');
|
31 |
+
$write->beginTransaction();
|
32 |
+
|
33 |
+
|
34 |
+
try
|
35 |
+
{
|
36 |
+
foreach ($groups as $group => $groupData)
|
37 |
+
{
|
38 |
+
foreach($groupData['fields'] as $name => $data)
|
39 |
+
{
|
40 |
+
if(isset($data['dependency']))
|
41 |
+
{
|
42 |
+
$path = $this->getHelper()->createPath($section, $group, $name);
|
43 |
+
$dependency = $this->getHelper()->getConfigDependency($path,$scope,$scopeId);
|
44 |
+
$source = 'default';
|
45 |
+
|
46 |
+
if(isset($data['source']))
|
47 |
+
{
|
48 |
+
$source = $this->getFirstElementIfArray($data['source']);
|
49 |
+
}
|
50 |
+
|
51 |
+
if(isset($data['inherit']) || $source == 'default')
|
52 |
+
{
|
53 |
+
$dependency->delete();
|
54 |
+
|
55 |
+
// Config values that are inherited have to be rememered, for later processing
|
56 |
+
$this->handleConfigs[] = array('path' => $path, 'scope' => $scope, 'scopeId' => $scopeId);
|
57 |
+
|
58 |
+
}
|
59 |
+
else
|
60 |
+
{
|
61 |
+
// Create dependency
|
62 |
+
$scopeInfo = $this->getHelper()->getScope($source);
|
63 |
+
$dependency->setDependsOnScope($scopeInfo['scope']);
|
64 |
+
$dependency->setDependsOnScopeId($scopeInfo['scope_id']);
|
65 |
+
$this->getHelper()->validateDependency($dependency);
|
66 |
+
$dependency->save();
|
67 |
+
|
68 |
+
|
69 |
+
// Save a config value
|
70 |
+
//$configModel = new Mage_Core_Model_Config();
|
71 |
+
//$configModel ->saveConfig('general/country/default', "US", $scope, $scopeId);
|
72 |
+
}
|
73 |
+
}
|
74 |
+
}
|
75 |
+
}
|
76 |
+
$write->commit();
|
77 |
+
}
|
78 |
+
catch(Exception $e)
|
79 |
+
{
|
80 |
+
while($write->getTransactionLevel() > 0)
|
81 |
+
{
|
82 |
+
$write->rollback();
|
83 |
+
}
|
84 |
+
throw $e;
|
85 |
+
}
|
86 |
+
}
|
87 |
+
|
88 |
+
/**
|
89 |
+
* This method is registered to the event 'core_config_data_delete_after'. All dependencies
|
90 |
+
* are resolved after deleting and inherited values are updated.
|
91 |
+
*
|
92 |
+
* @param Varien_Event_Observer $observer
|
93 |
+
*/
|
94 |
+
public function configDeleteAfter(Varien_Event_Observer $observer)
|
95 |
+
{
|
96 |
+
$this->configSaveAfter($observer);
|
97 |
+
}
|
98 |
+
|
99 |
+
/**
|
100 |
+
* This method is registered to the event 'core_config_data_save_after'. All dependencies
|
101 |
+
* are resolved after saving and inherited values are updated.
|
102 |
+
*
|
103 |
+
* @param Varien_Event_Observer $observer
|
104 |
+
*/
|
105 |
+
public function configSaveAfter(Varien_Event_Observer $observer)
|
106 |
+
{
|
107 |
+
// We keep track of wheter we are already in the dependency updating process
|
108 |
+
static $configUpdateInProgress = false;
|
109 |
+
|
110 |
+
if(!$configUpdateInProgress)
|
111 |
+
{
|
112 |
+
$configUpdateInProgress = true;
|
113 |
+
$configData = $observer->getEvent()->getConfigData();
|
114 |
+
|
115 |
+
$path = $configData->getPath();
|
116 |
+
$scope = $configData->getScope();
|
117 |
+
$scopeId = $configData->getScopeId();
|
118 |
+
|
119 |
+
$this->handleConfigs[] = array('path' => $path, 'scope' => $scope, 'scopeId' => $scopeId);
|
120 |
+
$this->saveUpdateConfig($this->handleConfigs);
|
121 |
+
|
122 |
+
$configUpdateInProgress = false;
|
123 |
+
}
|
124 |
+
}
|
125 |
+
|
126 |
+
/**
|
127 |
+
* This method listens to the event 'core_model_config_save_before'.
|
128 |
+
* This is a CUSTOM EVENT that is introduced in local/Mage/Core/Model/Config.php to
|
129 |
+
* handle changes in config values from code and not the UI.
|
130 |
+
* The dependency for the changed config value is removed if one exists and dependent values
|
131 |
+
* are updateted.
|
132 |
+
*
|
133 |
+
* @param Varien_Event_Observer $observer
|
134 |
+
*/
|
135 |
+
public function coreConfigSaveAfter(Varien_Event_Observer $observer)
|
136 |
+
{
|
137 |
+
$path = $observer->getPath();
|
138 |
+
$scope = $observer->getScope();
|
139 |
+
$scopeId = $observer->getScopeId();
|
140 |
+
|
141 |
+
$write = Mage::getSingleton('core/resource')->getConnection('core_write');
|
142 |
+
$write->beginTransaction();
|
143 |
+
|
144 |
+
try
|
145 |
+
{
|
146 |
+
$this->getHelper()->deleteDependenyOf($path,$scope,$scopeId);
|
147 |
+
$this->saveUpdateConfig(array('path' => $path, 'scope' => $scope, 'scopeId' => $scopeId));
|
148 |
+
}
|
149 |
+
catch(Exception $e)
|
150 |
+
{
|
151 |
+
while($write->getTransactionLevel() > 0)
|
152 |
+
{
|
153 |
+
$write->rollback();
|
154 |
+
}
|
155 |
+
throw $e;
|
156 |
+
}
|
157 |
+
|
158 |
+
if($write->getTransactionLevel() >= 1)
|
159 |
+
{
|
160 |
+
$write->commit();
|
161 |
+
}
|
162 |
+
}
|
163 |
+
|
164 |
+
/**
|
165 |
+
* Handles all the config values we need to update.
|
166 |
+
* @param array $configs
|
167 |
+
*/
|
168 |
+
protected function saveUpdateConfig($configs)
|
169 |
+
{
|
170 |
+
foreach($configs as $config)
|
171 |
+
{
|
172 |
+
$path = $config['path'];
|
173 |
+
$scope = $config['scope'];
|
174 |
+
$scopeId = $config['scopeId'];
|
175 |
+
$allConfigData = $this->getAllConfigs($path);
|
176 |
+
|
177 |
+
if($scope == 'default' || preg_match('/website/i',$scope) || !isset($allConfigData[$scope . '_' . $scopeId]))
|
178 |
+
{
|
179 |
+
foreach($allConfigData as $entity)
|
180 |
+
{
|
181 |
+
$this->updateAllChildren($path, $entity, $allConfigData);
|
182 |
+
}
|
183 |
+
}
|
184 |
+
else
|
185 |
+
{
|
186 |
+
$entity = $allConfigData[$scope . '_' . $scopeId];
|
187 |
+
$this->updateAllChildren($path, $entity, $allConfigData);
|
188 |
+
}
|
189 |
+
|
190 |
+
$this->saveAllConfigData($allConfigData);
|
191 |
+
}
|
192 |
+
}
|
193 |
+
|
194 |
+
/**
|
195 |
+
* Updates all children of $possibleChild in the dependency graph.
|
196 |
+
*
|
197 |
+
* @param string $path
|
198 |
+
* @param Mage_Core_Config_Data $possibleChild
|
199 |
+
* @param array $allConfigData
|
200 |
+
*/
|
201 |
+
protected function updateAllChildren($path, $possibleChild, $allConfigData)
|
202 |
+
{
|
203 |
+
$edgesToDependent = $this->getHelper()->getDependentConfigEdges($path);
|
204 |
+
$edgesFromDependent = $this->getHelper()->getDependeeConfigEdges($path);
|
205 |
+
$start = $possibleChild->getScope() . "_" . $possibleChild->getScopeId();
|
206 |
+
$updateRoot = false;
|
207 |
+
$value = $possibleChild->getValue();
|
208 |
+
|
209 |
+
// If current node has a parent in the dependency graph
|
210 |
+
if(isset($edgesFromDependent[$start]))
|
211 |
+
{
|
212 |
+
$parent = $edgesFromDependent[$start];
|
213 |
+
$value = $this->getParentValue($parent,$allConfigData,$path);
|
214 |
+
$updateRoot = true;
|
215 |
+
}
|
216 |
+
|
217 |
+
$this->dfs($start, $allConfigData, $edgesToDependent, $value ,$updateRoot);
|
218 |
+
}
|
219 |
+
|
220 |
+
private function dfs($node, $allNodes, $edges, $value, $update)
|
221 |
+
{
|
222 |
+
static $visited = array();
|
223 |
+
if(array_key_exists($node,$visited) && $visited[$node] == 1)
|
224 |
+
{
|
225 |
+
throw new Exception(Mage::helper('dependency')->__("Dependency loop detected."));
|
226 |
+
}
|
227 |
+
else
|
228 |
+
{
|
229 |
+
$visited[$node] = 1;
|
230 |
+
if($update)
|
231 |
+
{
|
232 |
+
$s = $allNodes[$node];
|
233 |
+
if($s->getValue() != $value)
|
234 |
+
{
|
235 |
+
$s->setValue($value);
|
236 |
+
}
|
237 |
+
}
|
238 |
+
|
239 |
+
if(isset($edges[$node]))
|
240 |
+
{
|
241 |
+
foreach($edges[$node] as $nextNode)
|
242 |
+
{
|
243 |
+
$this->dfs($nextNode, $allNodes, $edges, $value, true);
|
244 |
+
}
|
245 |
+
}
|
246 |
+
}
|
247 |
+
|
248 |
+
$visited[$node] = 0;
|
249 |
+
}
|
250 |
+
|
251 |
+
/**
|
252 |
+
* Returns the value of the $parent node. This is a bit tricky as config values
|
253 |
+
* that use default values do no longer exist in the database.
|
254 |
+
*
|
255 |
+
* @param string $parent
|
256 |
+
* @param array $allConfigData
|
257 |
+
* @param string $path
|
258 |
+
* @return mixed
|
259 |
+
*/
|
260 |
+
private function getParentValue($parent, $allConfigData, $path)
|
261 |
+
{
|
262 |
+
// If the parent uses default no config value exists.
|
263 |
+
if(!isset($allConfigData[$parent]))
|
264 |
+
{
|
265 |
+
$scope = $this->getHelper()->splitScopeString($parent);
|
266 |
+
|
267 |
+
if(preg_match("/store/i", $scope['scope']))
|
268 |
+
{
|
269 |
+
$websiteId = $this->storeToWebsite($scope['scope_id']);
|
270 |
+
|
271 |
+
// If the website config does not exist, back up to the default value.
|
272 |
+
if(!isset($allConfigData['websites_' . $websiteId]))
|
273 |
+
{
|
274 |
+
// If there is no default value in the db, backup to the confg xml
|
275 |
+
if(!isset($allConfigData['default_0']))
|
276 |
+
{
|
277 |
+
$value = Mage::getStoreConfig($path);
|
278 |
+
}
|
279 |
+
else
|
280 |
+
{
|
281 |
+
$value = $allConfigData['default_0']->getValue();
|
282 |
+
}
|
283 |
+
}
|
284 |
+
else
|
285 |
+
{
|
286 |
+
$value = $allConfigData['websites_' . $websiteId]->getValue();
|
287 |
+
}
|
288 |
+
}
|
289 |
+
else
|
290 |
+
{
|
291 |
+
$value = $allConfigData['default_0']->getValue();
|
292 |
+
}
|
293 |
+
}
|
294 |
+
else
|
295 |
+
{
|
296 |
+
$value = $allConfigData[$parent]->getValue();
|
297 |
+
}
|
298 |
+
return $value;
|
299 |
+
}
|
300 |
+
|
301 |
+
private function getFirstElementIfArray($possibleArray)
|
302 |
+
{
|
303 |
+
return is_array($possibleArray) ? $possibleArray[0] : $possibleArray;
|
304 |
+
}
|
305 |
+
|
306 |
+
/**
|
307 |
+
* Returns an array of all the stored config values for a given path
|
308 |
+
* @param string $path
|
309 |
+
* @return array
|
310 |
+
*/
|
311 |
+
private function getAllConfigs($path)
|
312 |
+
{
|
313 |
+
$allConfigs = array();
|
314 |
+
$configDataCollection = Mage::getModel('core/config_data')->getCollection()
|
315 |
+
->addFieldToFilter('path', array('like' => "%" . $path . '%'));
|
316 |
+
|
317 |
+
foreach ($configDataCollection as $data) {
|
318 |
+
$allConfigs[$data->getScope() . '_' . $data->getScopeId()] = $data;
|
319 |
+
}
|
320 |
+
|
321 |
+
return $allConfigs;
|
322 |
+
}
|
323 |
+
|
324 |
+
/**
|
325 |
+
* Returns the website id for a given store id;
|
326 |
+
* @param int $id
|
327 |
+
*/
|
328 |
+
private function storeToWebsite($id)
|
329 |
+
{
|
330 |
+
if($this->storeToWebsiteMap == null)
|
331 |
+
{
|
332 |
+
$this->storeToWebsiteMap = array();
|
333 |
+
$storeModel = Mage::getSingleton('adminhtml/system_store');
|
334 |
+
foreach ($storeModel->getStoreCollection() as $store) {
|
335 |
+
$this->storeToWebsiteMap[$store->getId()] = $store->getWebsiteId();
|
336 |
+
}
|
337 |
+
}
|
338 |
+
return $this->storeToWebsiteMap[$id];
|
339 |
+
}
|
340 |
+
|
341 |
+
/**
|
342 |
+
* Stores all the configuration data to the database.
|
343 |
+
* @param array $allConfigData
|
344 |
+
*/
|
345 |
+
private function saveAllConfigData($allConfigData)
|
346 |
+
{
|
347 |
+
foreach($allConfigData as $data){
|
348 |
+
$data->save();
|
349 |
+
}
|
350 |
+
}
|
351 |
+
|
352 |
+
private function getHelper()
|
353 |
+
{
|
354 |
+
if($this->helper == null)
|
355 |
+
{
|
356 |
+
$this->helper = Mage::helper('dependency/config');
|
357 |
+
}
|
358 |
+
return $this->helper;
|
359 |
+
}
|
360 |
+
|
361 |
+
private $helper = null;
|
362 |
+
private $handleConfigs = array();
|
363 |
+
private $storeToWebsiteMap = null;
|
364 |
+
}
|
app/code/local/Customweb/Dependency/Model/Mysql4/Category.php
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
::[Header]::
|
4 |
+
* @category Local
|
5 |
+
* @package Customweb_Dependency
|
6 |
+
* @link http://www.customweb.ch
|
7 |
+
*/
|
8 |
+
|
9 |
+
class Customweb_Dependency_Model_Mysql4_Category extends Mage_Core_Model_Mysql4_Abstract{
|
10 |
+
protected function _construct()
|
11 |
+
{
|
12 |
+
$this->_init('dependency/category', 'dependency_id');
|
13 |
+
}
|
14 |
+
}
|
app/code/local/Customweb/Dependency/Model/Mysql4/Category/Collection.php
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
::[Header]::
|
4 |
+
* @category Local
|
5 |
+
* @package Customweb_Dependency
|
6 |
+
* @link http://www.customweb.ch
|
7 |
+
*/
|
8 |
+
|
9 |
+
class Customweb_Dependency_Model_Mysql4_Category_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract {
|
10 |
+
protected function _construct()
|
11 |
+
{
|
12 |
+
$this->_init('dependency/category');
|
13 |
+
}
|
14 |
+
}
|
app/code/local/Customweb/Dependency/Model/Mysql4/Config.php
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
::[Header]::
|
5 |
+
* @category Local
|
6 |
+
* @package Customweb_Dependency
|
7 |
+
* @link http://www.customweb.ch
|
8 |
+
*/
|
9 |
+
|
10 |
+
class Customweb_Dependency_Model_Mysql4_Config extends Mage_Core_Model_Mysql4_Abstract{
|
11 |
+
protected function _construct()
|
12 |
+
{
|
13 |
+
$this->_init('dependency/config', 'dependency_id');
|
14 |
+
}
|
15 |
+
}
|
app/code/local/Customweb/Dependency/Model/Mysql4/Config/Collection.php
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
::[Header]::
|
4 |
+
* @category Local
|
5 |
+
* @package Customweb_Dependency
|
6 |
+
* @link http://www.customweb.ch
|
7 |
+
*/
|
8 |
+
|
9 |
+
class Customweb_Dependency_Model_Mysql4_Config_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract {
|
10 |
+
protected function _construct()
|
11 |
+
{
|
12 |
+
$this->_init('dependency/config');
|
13 |
+
}
|
14 |
+
}
|
app/code/local/Customweb/Dependency/Model/Mysql4/Product.php
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
::[Header]::
|
5 |
+
* @category Local
|
6 |
+
* @package Customweb_Dependency
|
7 |
+
* @link http://www.customweb.ch
|
8 |
+
*/
|
9 |
+
|
10 |
+
class Customweb_Dependency_Model_Mysql4_Product extends Mage_Core_Model_Mysql4_Abstract{
|
11 |
+
protected function _construct()
|
12 |
+
{
|
13 |
+
$this->_init('dependency/product', 'dependency_id');
|
14 |
+
}
|
15 |
+
}
|
app/code/local/Customweb/Dependency/Model/Mysql4/Product/Collection.php
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
::[Header]::
|
5 |
+
* @category Local
|
6 |
+
* @package Customweb_Dependency
|
7 |
+
* @link http://www.customweb.ch
|
8 |
+
*/
|
9 |
+
|
10 |
+
class Customweb_Dependency_Model_Mysql4_Product_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract {
|
11 |
+
protected function _construct()
|
12 |
+
{
|
13 |
+
$this->_init('dependency/product');
|
14 |
+
}
|
15 |
+
}
|
app/code/local/Customweb/Dependency/Model/Product.php
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
::[Header]::
|
5 |
+
* @category Local
|
6 |
+
* @package Customweb_Dependency
|
7 |
+
* @link http://www.customweb.ch
|
8 |
+
*/
|
9 |
+
|
10 |
+
class Customweb_Dependency_Model_Product extends Mage_Core_Model_Abstract
|
11 |
+
{
|
12 |
+
protected function _construct()
|
13 |
+
{
|
14 |
+
$this->_init('dependency/product');
|
15 |
+
}
|
16 |
+
|
17 |
+
}
|
app/code/local/Customweb/Dependency/Model/Resource/Setup.php
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
::[Header]::
|
4 |
+
* @category Local
|
5 |
+
* @package Customweb_Dependencies
|
6 |
+
* @link http://www.customweb.ch
|
7 |
+
*/
|
8 |
+
|
9 |
+
class Customweb_Dependency_Model_Resource_Setup extends Mage_Core_Model_Resource_Setup
|
10 |
+
{
|
11 |
+
|
12 |
+
}
|
app/code/local/Customweb/Dependency/controllers/TestController.php
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class Customweb_Dependency_TestController extends Mage_Core_Controller_Front_Action
|
4 |
+
{
|
5 |
+
public function indexAction(){
|
6 |
+
$model = new Mage_Core_Model_Config();
|
7 |
+
//$model->saveConfig('esr/account_information/deposit_for', "Geänadert auf website level", 'websites', 2);
|
8 |
+
//$model->saveConfig('esr/account_information/deposit_for', "geändert auf store level", 'stores', 2);
|
9 |
+
$model->saveConfig('esr/account_information/deposit_for', "default 88");
|
10 |
+
die("fertig");
|
11 |
+
}
|
12 |
+
}
|
app/code/local/Customweb/Dependency/etc/config.xml
ADDED
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
2 |
+
|
3 |
+
<config>
|
4 |
+
<modules>
|
5 |
+
<Customweb_Dependency>
|
6 |
+
<version>1.0.0</version>
|
7 |
+
</Customweb_Dependency>
|
8 |
+
</modules>
|
9 |
+
|
10 |
+
<global>
|
11 |
+
<models>
|
12 |
+
<dependency>
|
13 |
+
<class>Customweb_Dependency_Model</class>
|
14 |
+
<resourceModel>dependency_mysql4</resourceModel>
|
15 |
+
</dependency>
|
16 |
+
<dependency_mysql4>
|
17 |
+
<class>Customweb_Dependency_Model_Mysql4</class>
|
18 |
+
<entities>
|
19 |
+
<product>
|
20 |
+
<table>customweb_product_dependency</table>
|
21 |
+
</product>
|
22 |
+
<category>
|
23 |
+
<table>customweb_category_dependency</table>
|
24 |
+
</category>
|
25 |
+
<config>
|
26 |
+
<table>customweb_config_dependency</table>
|
27 |
+
</config>
|
28 |
+
</entities>
|
29 |
+
</dependency_mysql4>
|
30 |
+
</models>
|
31 |
+
<helpers>
|
32 |
+
<dependency>
|
33 |
+
<class>Customweb_Dependency_Helper</class>
|
34 |
+
</dependency>
|
35 |
+
</helpers>
|
36 |
+
<resources>
|
37 |
+
<dependency_write>
|
38 |
+
<connection>
|
39 |
+
<use>core_write</use>
|
40 |
+
</connection>
|
41 |
+
</dependency_write>
|
42 |
+
<dependency_read>
|
43 |
+
<connection>
|
44 |
+
<use>core_read</use>
|
45 |
+
</connection>
|
46 |
+
</dependency_read>
|
47 |
+
<dependency_setup>
|
48 |
+
<setup>
|
49 |
+
<module>Customweb_Dependency</module>
|
50 |
+
<class>Customweb_Dependency_Model_Resource_Setup</class>
|
51 |
+
</setup>
|
52 |
+
</dependency_setup>
|
53 |
+
</resources>
|
54 |
+
<events>
|
55 |
+
<core_model_config_save_after>
|
56 |
+
<observers>
|
57 |
+
<dependency_core_config_save_after>
|
58 |
+
<type>singleton</type>
|
59 |
+
<class>Customweb_Dependency_Model_ConfigObserver</class>
|
60 |
+
<method>coreConfigSaveAfter</method>
|
61 |
+
</dependency_core_config_save_after>
|
62 |
+
</observers>
|
63 |
+
</core_model_config_save_after>
|
64 |
+
<model_config_data_save_before>
|
65 |
+
<observers>
|
66 |
+
<dependency_config_save_before>
|
67 |
+
<type>singleton</type>
|
68 |
+
<class>Customweb_Dependency_Model_ConfigObserver</class>
|
69 |
+
<method>configSaveBefore</method>
|
70 |
+
</dependency_config_save_before>
|
71 |
+
</observers>
|
72 |
+
</model_config_data_save_before>
|
73 |
+
<core_config_data_save_after>
|
74 |
+
<observers>
|
75 |
+
<dependency_config_save_after>
|
76 |
+
<type>singleton</type>
|
77 |
+
<class>Customweb_Dependency_Model_ConfigObserver</class>
|
78 |
+
<method>configSaveAfter</method>
|
79 |
+
</dependency_config_save_after>
|
80 |
+
</observers>
|
81 |
+
</core_config_data_save_after>
|
82 |
+
<core_config_data_delete_after>
|
83 |
+
<observers>
|
84 |
+
<dependency_config_delete_after>
|
85 |
+
<type>singleton</type>
|
86 |
+
<class>Customweb_Dependency_Model_ConfigObserver</class>
|
87 |
+
<method>configDeleteAfter</method>
|
88 |
+
</dependency_config_delete_after>
|
89 |
+
</observers>
|
90 |
+
</core_config_data_delete_after>
|
91 |
+
|
92 |
+
|
93 |
+
|
94 |
+
</events>
|
95 |
+
</global>
|
96 |
+
|
97 |
+
<adminhtml>
|
98 |
+
<layout>
|
99 |
+
<updates>
|
100 |
+
<dependency>
|
101 |
+
<file>dependency.xml</file>
|
102 |
+
</dependency>
|
103 |
+
</updates>
|
104 |
+
</layout>
|
105 |
+
</adminhtml>
|
106 |
+
|
107 |
+
|
108 |
+
</config>
|
app/code/local/Customweb/Dependency/sql/dependency_setup/mysql4-install-1.0.0.php
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
::[Header]::
|
5 |
+
* @category Local
|
6 |
+
* @package Customweb_Dependency
|
7 |
+
* @link http://www.customweb.ch
|
8 |
+
*/
|
9 |
+
|
10 |
+
$installer = $this;
|
11 |
+
$installer->startSetup();
|
12 |
+
$installer->run("CREATE TABLE `customweb_product_dependency` (
|
13 |
+
`dependency_id` INT NOT NULL AUTO_INCREMENT,
|
14 |
+
`attribute_id` INT NOT NULL ,
|
15 |
+
`store_id` INT NOT NULL ,
|
16 |
+
`entity_id` INT NOT NULL ,
|
17 |
+
`depends_on_store_id` INT NOT NULL ,
|
18 |
+
PRIMARY KEY ( `dependency_id` )
|
19 |
+
) ENGINE = InnoDB DEFAULT CHARSET=utf8;
|
20 |
+
");
|
21 |
+
|
22 |
+
$installer->run("CREATE TABLE `customweb_category_dependency` (
|
23 |
+
`dependency_id` INT NOT NULL AUTO_INCREMENT,
|
24 |
+
`attribute_id` INT NOT NULL ,
|
25 |
+
`store_id` INT NOT NULL ,
|
26 |
+
`entity_id` INT NOT NULL ,
|
27 |
+
`depends_on_store_id` INT NOT NULL ,
|
28 |
+
PRIMARY KEY ( `dependency_id` )
|
29 |
+
) ENGINE = InnoDB DEFAULT CHARSET=utf8;
|
30 |
+
");
|
31 |
+
|
32 |
+
$installer->run("CREATE TABLE `customweb_config_dependency` (
|
33 |
+
`dependency_id` INT NOT NULL AUTO_INCREMENT,
|
34 |
+
`path` VARCHAR(255) NOT NULL ,
|
35 |
+
`scope` VARCHAR(8) NOT NULL ,
|
36 |
+
`scope_id` INT NOT NULL ,
|
37 |
+
`depends_on_scope` VARCHAR(8) NOT NULL ,
|
38 |
+
`depends_on_scope_id` INT NOT NULL ,
|
39 |
+
PRIMARY KEY ( `dependency_id` )
|
40 |
+
) ENGINE = InnoDB DEFAULT CHARSET=utf8;
|
41 |
+
");
|
42 |
+
|
43 |
+
$installer->endSetup();
|
44 |
+
|
45 |
+
|
app/code/local/Mage/Core/Model/Config.php
ADDED
@@ -0,0 +1,1625 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|