1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:
26:
27: 28: 29: 30: 31: 32: 33:
34: class Mage_Core_Model_Resource_Setup
35: {
36: const DEFAULT_SETUP_CONNECTION = 'core_setup';
37: const VERSION_COMPARE_EQUAL = 0;
38: const VERSION_COMPARE_LOWER = -1;
39: const VERSION_COMPARE_GREATER = 1;
40:
41: const TYPE_DB_INSTALL = 'install';
42: const TYPE_DB_UPGRADE = 'upgrade';
43: const TYPE_DB_ROLLBACK = 'rollback';
44: const TYPE_DB_UNINSTALL = 'uninstall';
45: const TYPE_DATA_INSTALL = 'data-install';
46: const TYPE_DATA_UPGRADE = 'data-upgrade';
47:
48: 49: 50: 51:
52: protected $_resourceName;
53:
54: 55: 56: 57: 58:
59: protected $_resourceConfig;
60:
61: 62: 63: 64: 65:
66: protected $_connectionConfig;
67:
68: 69: 70: 71: 72:
73: protected $_moduleConfig;
74:
75: 76: 77: 78: 79:
80: protected $_callAfterApplyAllUpdates = false;
81:
82: 83: 84: 85: 86:
87: protected $_conn;
88: 89: 90: 91: 92:
93: protected $_tables = array();
94: 95: 96: 97: 98:
99: protected $_setupCache = array();
100:
101: 102: 103: 104: 105:
106: protected $_queriesHooked = false;
107:
108: 109: 110: 111: 112:
113: protected static $_hadUpdates;
114:
115: 116: 117: 118: 119:
120: protected static $_schemaUpdatesChecked;
121:
122: 123: 124: 125: 126:
127: public function __construct($resourceName)
128: {
129: $config = Mage::getConfig();
130: $this->_resourceName = $resourceName;
131: $this->_resourceConfig = $config->getResourceConfig($resourceName);
132: $connection = $config->getResourceConnectionConfig($resourceName);
133: if ($connection) {
134: $this->_connectionConfig = $connection;
135: } else {
136: $this->_connectionConfig = $config->getResourceConnectionConfig(self::DEFAULT_SETUP_CONNECTION);
137: }
138:
139: $modName = (string)$this->_resourceConfig->setup->module;
140: $this->_moduleConfig = $config->getModuleConfig($modName);
141: $connection = Mage::getSingleton('core/resource')->getConnection($this->_resourceName);
142: 143: 144:
145: if (!$connection) {
146: $connection = Mage::getSingleton('core/resource')->getConnection($this->_resourceName);
147: }
148: $this->_conn = $connection;
149: }
150:
151: 152: 153: 154: 155:
156: public function getConnection()
157: {
158: return $this->_conn;
159: }
160:
161: 162: 163: 164: 165: 166: 167:
168: public function setTable($tableName, $realTableName)
169: {
170: $this->_tables[$tableName] = $realTableName;
171: return $this;
172: }
173:
174: 175: 176: 177: 178: 179:
180: public function getTable($tableName)
181: {
182: $cacheKey = $this->_getTableCacheName($tableName);
183: if (!isset($this->_tables[$cacheKey])) {
184: $this->_tables[$cacheKey] = Mage::getSingleton('core/resource')->getTableName($tableName);
185: }
186: return $this->_tables[$cacheKey];
187: }
188:
189: 190: 191: 192: 193: 194:
195: protected function _getTableCacheName($tableName)
196: {
197: if (is_array($tableName)) {
198: return join('_', $tableName);
199:
200: }
201: return $tableName;
202: }
203:
204: 205: 206: 207: 208:
209: protected function _getResource()
210: {
211: return Mage::getResourceSingleton('core/resource');
212: }
213:
214: 215: 216: 217: 218:
219: static public function applyAllUpdates()
220: {
221: Mage::app()->setUpdateMode(true);
222: self::$_hadUpdates = false;
223:
224: $resources = Mage::getConfig()->getNode('global/resources')->children();
225: $afterApplyUpdates = array();
226: foreach ($resources as $resName => $resource) {
227: if (!$resource->setup) {
228: continue;
229: }
230: $className = __CLASS__;
231: if (isset($resource->setup->class)) {
232: $className = $resource->setup->getClassName();
233: }
234: $setupClass = new $className($resName);
235: $setupClass->applyUpdates();
236: if ($setupClass->getCallAfterApplyAllUpdates()) {
237: $afterApplyUpdates[] = $setupClass;
238: }
239: }
240:
241: foreach ($afterApplyUpdates as $setupClass) {
242: $setupClass->afterApplyAllUpdates();
243: }
244:
245: Mage::app()->setUpdateMode(false);
246: self::$_schemaUpdatesChecked = true;
247: return true;
248: }
249:
250: 251: 252: 253:
254: static public function applyAllDataUpdates()
255: {
256: if (!self::$_schemaUpdatesChecked) {
257: return;
258: }
259: $resources = Mage::getConfig()->getNode('global/resources')->children();
260: foreach ($resources as $resName => $resource) {
261: if (!$resource->setup) {
262: continue;
263: }
264: $className = __CLASS__;
265: if (isset($resource->setup->class)) {
266: $className = $resource->setup->getClassName();
267: }
268: $setupClass = new $className($resName);
269: $setupClass->applyDataUpdates();
270: }
271: }
272:
273: 274: 275: 276: 277: 278:
279: public function applyDataUpdates()
280: {
281: $dataVer= $this->_getResource()->getDataVersion($this->_resourceName);
282: $configVer = (string)$this->_moduleConfig->version;
283: if ($dataVer !== false) {
284: $status = version_compare($configVer, $dataVer);
285: if ($status == self::VERSION_COMPARE_GREATER) {
286: $this->_upgradeData($dataVer, $configVer);
287: }
288: } elseif ($configVer) {
289: $this->_installData($configVer);
290: }
291: return $this;
292: }
293:
294: 295: 296: 297: 298:
299: public function applyUpdates()
300: {
301: $dbVer = $this->_getResource()->getDbVersion($this->_resourceName);
302: $configVer = (string)$this->_moduleConfig->version;
303:
304: 305: 306: 307:
308: if (((string)$this->_moduleConfig->codePool != 'core') && Mage::helper('core')->useDbCompatibleMode()) {
309: $this->_hookQueries();
310: }
311:
312:
313: if ($dbVer !== false) {
314: $status = version_compare($configVer, $dbVer);
315: switch ($status) {
316: case self::VERSION_COMPARE_LOWER:
317: $this->_rollbackResourceDb($configVer, $dbVer);
318: break;
319: case self::VERSION_COMPARE_GREATER:
320: $this->_upgradeResourceDb($dbVer, $configVer);
321: break;
322: default:
323: return true;
324: break;
325: }
326: } elseif ($configVer) {
327: $this->_installResourceDb($configVer);
328: }
329:
330: $this->_unhookQueries();
331:
332: return $this;
333: }
334:
335: 336: 337: 338: 339: 340: 341:
342: protected function _hookQueries()
343: {
344: $this->_queriesHooked = true;
345:
346: $adapter = $this->getConnection();
347: $adapter->setQueryHook(array('object' => $this, 'method' => 'callbackQueryHook'));
348: return $this;
349: }
350:
351: 352: 353: 354: 355:
356: protected function _unhookQueries()
357: {
358: if (!$this->_queriesHooked) {
359: return $this;
360: }
361:
362: $adapter = $this->getConnection();
363: $adapter->setQueryHook(null);
364: $this->_queriesHooked = false;
365: return $this;
366: }
367:
368: 369: 370: 371: 372: 373: 374: 375:
376: public function callbackQueryHook(&$sql, &$bind)
377: {
378: Mage::getSingleton('core/resource_setup_query_modifier', array($this->getConnection()))
379: ->processQuery($sql, $bind);
380: return $this;
381: }
382:
383: 384: 385: 386: 387: 388:
389: protected function _installData($newVersion)
390: {
391: $oldVersion = $this->_modifyResourceDb(self::TYPE_DATA_INSTALL, '', $newVersion);
392: $this->_modifyResourceDb(self::TYPE_DATA_UPGRADE, $oldVersion, $newVersion);
393: $this->_getResource()->setDataVersion($this->_resourceName, $newVersion);
394:
395: return $this;
396: }
397:
398: 399: 400: 401: 402: 403: 404:
405: protected function _upgradeData($oldVersion, $newVersion)
406: {
407: $this->_modifyResourceDb('data-upgrade', $oldVersion, $newVersion);
408: $this->_getResource()->setDataVersion($this->_resourceName, $newVersion);
409:
410: return $this;
411: }
412:
413: 414: 415: 416: 417: 418:
419: protected function _installResourceDb($newVersion)
420: {
421: $oldVersion = $this->_modifyResourceDb(self::TYPE_DB_INSTALL, '', $newVersion);
422: $this->_modifyResourceDb(self::TYPE_DB_UPGRADE, $oldVersion, $newVersion);
423: $this->_getResource()->setDbVersion($this->_resourceName, $newVersion);
424:
425: return $this;
426: }
427:
428: 429: 430: 431: 432: 433: 434:
435: protected function _upgradeResourceDb($oldVersion, $newVersion)
436: {
437: $this->_modifyResourceDb(self::TYPE_DB_UPGRADE, $oldVersion, $newVersion);
438: $this->_getResource()->setDbVersion($this->_resourceName, $newVersion);
439:
440: return $this;
441: }
442:
443: 444: 445: 446: 447: 448: 449:
450: protected function _rollbackResourceDb($newVersion, $oldVersion)
451: {
452: $this->_modifyResourceDb(self::TYPE_DB_ROLLBACK, $newVersion, $oldVersion);
453: return $this;
454: }
455:
456: 457: 458: 459: 460: 461:
462: protected function _uninstallResourceDb($version)
463: {
464: $this->_modifyResourceDb(self::TYPE_DB_UNINSTALL, $version, '');
465: return $this;
466: }
467:
468: 469: 470: 471: 472: 473: 474: 475:
476: protected function _getAvailableDbFiles($actionType, $fromVersion, $toVersion)
477: {
478: $resModel = (string)$this->_connectionConfig->model;
479: $modName = (string)$this->_moduleConfig[0]->getName();
480:
481: $filesDir = Mage::getModuleDir('sql', $modName) . DS . $this->_resourceName;
482: if (!is_dir($filesDir) || !is_readable($filesDir)) {
483: return array();
484: }
485:
486: $dbFiles = array();
487: $typeFiles = array();
488: $regExpDb = sprintf('#^%s-(.*)\.(php|sql)$#i', $actionType);
489: $regExpType = sprintf('#^%s-%s-(.*)\.(php|sql)$#i', $resModel, $actionType);
490: $handlerDir = dir($filesDir);
491: while (false !== ($file = $handlerDir->read())) {
492: $matches = array();
493: if (preg_match($regExpDb, $file, $matches)) {
494: $dbFiles[$matches[1]] = $filesDir . DS . $file;
495: } else if (preg_match($regExpType, $file, $matches)) {
496: $typeFiles[$matches[1]] = $filesDir . DS . $file;
497: }
498: }
499: $handlerDir->close();
500:
501: if (empty($typeFiles) && empty($dbFiles)) {
502: return array();
503: }
504:
505: foreach ($typeFiles as $version => $file) {
506: $dbFiles[$version] = $file;
507: }
508:
509: return $this->_getModifySqlFiles($actionType, $fromVersion, $toVersion, $dbFiles);
510: }
511:
512: 513: 514: 515: 516: 517: 518: 519:
520: protected function _getAvailableDataFiles($actionType, $fromVersion, $toVersion)
521: {
522: $modName = (string)$this->_moduleConfig[0]->getName();
523: $files = array();
524:
525: $filesDir = Mage::getModuleDir('data', $modName) . DS . $this->_resourceName;
526: if (is_dir($filesDir) && is_readable($filesDir)) {
527: $regExp = sprintf('#^%s-(.*)\.php$#i', $actionType);
528: $handlerDir = dir($filesDir);
529: while (false !== ($file = $handlerDir->read())) {
530: $matches = array();
531: if (preg_match($regExp, $file, $matches)) {
532: $files[$matches[1]] = $filesDir . DS . $file;
533: }
534:
535: }
536: $handlerDir->close();
537: }
538:
539:
540: $filesDir = Mage::getModuleDir('sql', $modName) . DS . $this->_resourceName;
541: if (is_dir($filesDir) && is_readable($filesDir)) {
542: $regExp = sprintf('#^%s-%s-(.*)\.php$#i', $this->_connectionConfig->model, $actionType);
543: $handlerDir = dir($filesDir);
544:
545: while (false !== ($file = $handlerDir->read())) {
546: $matches = array();
547: if (preg_match($regExp, $file, $matches)) {
548: $files[$matches[1]] = $filesDir . DS . $file;
549: }
550: }
551: $handlerDir->close();
552: }
553:
554: if (empty($files)) {
555: return array();
556: }
557:
558: return $this->_getModifySqlFiles($actionType, $fromVersion, $toVersion, $files);
559: }
560:
561: 562: 563: 564: 565: 566: 567:
568: protected function _setResourceVersion($actionType, $version)
569: {
570: switch ($actionType) {
571: case self::TYPE_DB_INSTALL:
572: case self::TYPE_DB_UPGRADE:
573: $this->_getResource()->setDbVersion($this->_resourceName, $version);
574: break;
575: case self::TYPE_DATA_INSTALL:
576: case self::TYPE_DATA_UPGRADE:
577: $this->_getResource()->setDataVersion($this->_resourceName, $version);
578: break;
579:
580: }
581:
582: return $this;
583: }
584:
585: 586: 587: 588: 589: 590: 591: 592: 593:
594:
595: protected function _modifyResourceDb($actionType, $fromVersion, $toVersion)
596: {
597: switch ($actionType) {
598: case self::TYPE_DB_INSTALL:
599: case self::TYPE_DB_UPGRADE:
600: $files = $this->_getAvailableDbFiles($actionType, $fromVersion, $toVersion);
601: break;
602: case self::TYPE_DATA_INSTALL:
603: case self::TYPE_DATA_UPGRADE:
604: $files = $this->_getAvailableDataFiles($actionType, $fromVersion, $toVersion);
605: break;
606: default:
607: $files = array();
608: break;
609: }
610: if (empty($files) || !$this->getConnection()) {
611: return false;
612: }
613:
614: $version = false;
615:
616: foreach ($files as $file) {
617: $fileName = $file['fileName'];
618: $fileType = pathinfo($fileName, PATHINFO_EXTENSION);
619: $this->getConnection()->disallowDdlCache();
620: try {
621: switch ($fileType) {
622: case 'php':
623: $conn = $this->getConnection();
624: $result = include $fileName;
625: break;
626: case 'sql':
627: $sql = file_get_contents($fileName);
628: if (!empty($sql)) {
629:
630: $result = $this->run($sql);
631: } else {
632: $result = true;
633: }
634: break;
635: default:
636: $result = false;
637: break;
638: }
639:
640: if ($result) {
641: $this->_setResourceVersion($actionType, $file['toVersion']);
642: }
643: } catch (Exception $e) {
644: printf('<pre>%s</pre>', print_r($e, true));
645: throw Mage::exception('Mage_Core', Mage::helper('core')->__('Error in file: "%s" - %s', $fileName, $e->getMessage()));
646: }
647: $version = $file['toVersion'];
648: $this->getConnection()->allowDdlCache();
649: }
650: self::$_hadUpdates = true;
651: return $version;
652: }
653:
654: 655: 656: 657: 658: 659: 660: 661: 662:
663: protected function _getModifySqlFiles($actionType, $fromVersion, $toVersion, $arrFiles)
664: {
665: $arrRes = array();
666: switch ($actionType) {
667: case self::TYPE_DB_INSTALL:
668: case self::TYPE_DATA_INSTALL:
669: uksort($arrFiles, 'version_compare');
670: foreach ($arrFiles as $version => $file) {
671: if (version_compare($version, $toVersion) !== self::VERSION_COMPARE_GREATER) {
672: $arrRes[0] = array(
673: 'toVersion' => $version,
674: 'fileName' => $file
675: );
676: }
677: }
678: break;
679:
680: case self::TYPE_DB_UPGRADE:
681: case self::TYPE_DATA_UPGRADE:
682: uksort($arrFiles, 'version_compare');
683: foreach ($arrFiles as $version => $file) {
684: $versionInfo = explode('-', $version);
685:
686:
687: if (count($versionInfo)!=2) {
688: break;
689: }
690: $infoFrom = $versionInfo[0];
691: $infoTo = $versionInfo[1];
692: if (version_compare($infoFrom, $fromVersion) !== self::VERSION_COMPARE_LOWER
693: && version_compare($infoTo, $toVersion) !== self::VERSION_COMPARE_GREATER) {
694: $arrRes[] = array(
695: 'toVersion' => $infoTo,
696: 'fileName' => $file
697: );
698: }
699: }
700: break;
701:
702: case self::TYPE_DB_ROLLBACK:
703: break;
704:
705: case self::TYPE_DB_UNINSTALL:
706: break;
707: }
708: return $arrRes;
709: }
710:
711:
712:
713:
714: 715: 716: 717: 718: 719: 720: 721: 722: 723: 724:
725: public function getTableRow($table, $idField, $id, $field=null, $parentField=null, $parentId=0)
726: {
727: if (strpos($table, '/') !== false) {
728: $table = $this->getTable($table);
729: }
730:
731: if (empty($this->_setupCache[$table][$parentId][$id])) {
732: $adapter = $this->getConnection();
733: $bind = array('id_field' => $id);
734: $select = $adapter->select()
735: ->from($table)
736: ->where($adapter->quoteIdentifier($idField) . '= :id_field');
737: if (!is_null($parentField)) {
738: $select->where($adapter->quoteIdentifier($parentField) . '= :parent_id');
739: $bind['parent_id'] = $parentId;
740: }
741: $this->_setupCache[$table][$parentId][$id] = $adapter->fetchRow($select, $bind);
742: }
743:
744: if (is_null($field)) {
745: return $this->_setupCache[$table][$parentId][$id];
746: }
747: return isset($this->_setupCache[$table][$parentId][$id][$field])
748: ? $this->_setupCache[$table][$parentId][$id][$field]
749: : false;
750: }
751:
752:
753: 754: 755: 756: 757: 758: 759: 760: 761: 762:
763: public function deleteTableRow($table, $idField, $id, $parentField = null, $parentId = 0)
764: {
765: if (strpos($table, '/') !== false) {
766: $table = $this->getTable($table);
767: }
768:
769: $adapter = $this->getConnection();
770: $where = array($adapter->quoteIdentifier($idField) . '=?' => $id);
771: if (!is_null($parentField)) {
772: $where[$adapter->quoteIdentifier($parentField) . '=?'] = $parentId;
773: }
774:
775: $adapter->delete($table, $where);
776:
777: if (isset($this->_setupCache[$table][$parentId][$id])) {
778: unset($this->_setupCache[$table][$parentId][$id]);
779: }
780:
781: return $this;
782: }
783:
784: 785: 786: 787: 788: 789: 790: 791: 792: 793: 794: 795:
796: public function updateTableRow($table, $idField, $id, $field, $value = null, $parentField = null, $parentId = 0)
797: {
798: if (strpos($table, '/') !== false) {
799: $table = $this->getTable($table);
800: }
801:
802: if (is_array($field)) {
803: $data = $field;
804: } else {
805: $data = array($field => $value);
806: }
807:
808: $adapter = $this->getConnection();
809: $where = array($adapter->quoteIdentifier($idField) . '=?' => $id);
810: $adapter->update($table, $data, $where);
811:
812: if (isset($this->_setupCache[$table][$parentId][$id])) {
813: if (is_array($field)) {
814: $this->_setupCache[$table][$parentId][$id] =
815: array_merge($this->_setupCache[$table][$parentId][$id], $field);
816: } else {
817: $this->_setupCache[$table][$parentId][$id][$field] = $value;
818: }
819: }
820:
821: return $this;
822: }
823:
824: 825: 826: 827: 828: 829: 830: 831: 832: 833:
834: public function updateTable($table, $conditionExpr, $valueExpr)
835: {
836: if (strpos($table, '/') !== false) {
837: $table = $this->getTable($table);
838: }
839: $query = sprintf('UPDATE %s SET %s WHERE %s',
840: $this->getConnection()->quoteIdentifier($table),
841: $conditionExpr,
842: $valueExpr);
843:
844: $this->getConnection()->query($query);
845:
846: return $this;
847: }
848:
849: 850: 851: 852: 853: 854:
855: public function tableExists($table)
856: {
857: if (strpos($table, '/') !== false) {
858: $table = $this->getTable($table);
859: }
860:
861: return $this->getConnection()->isTableExists($table);
862:
863: }
864:
865:
866:
867: 868: 869: 870: 871: 872: 873: 874: 875: 876:
877: public function addConfigField($path, $label, array $data=array(), $default=null)
878: {
879: return $this;
880: }
881:
882: 883: 884: 885: 886: 887: 888: 889: 890: 891:
892: public function setConfigData($path, $value, $scope = 'default', $scopeId = 0, $inherit=0)
893: {
894: $table = $this->getTable('core/config_data');
895:
896: $this->getConnection()->showTableStatus($table);
897:
898: $data = array(
899: 'scope' => $scope,
900: 'scope_id' => $scopeId,
901: 'path' => $path,
902: 'value' => $value
903: );
904: $this->getConnection()->insertOnDuplicate($table, $data, array('value'));
905: return $this;
906: }
907:
908: 909: 910: 911: 912: 913: 914:
915: public function deleteConfigData($path, $scope = null)
916: {
917: $where = array('path = ?' => $path);
918: if (!is_null($scope)) {
919: $where['scope = ?'] = $scope;
920: }
921: $this->getConnection()->delete($this->getTable('core/config_data'), $where);
922: return $this;
923: }
924:
925: 926: 927: 928: 929: 930:
931: public function run($sql)
932: {
933: $this->getConnection()->multiQuery($sql);
934: return $this;
935: }
936:
937: 938: 939: 940: 941:
942: public function startSetup()
943: {
944: $this->getConnection()->startSetup();
945: return $this;
946: }
947:
948: 949: 950: 951: 952:
953: public function endSetup()
954: {
955: $this->getConnection()->endSetup();
956: return $this;
957: }
958:
959: 960: 961: 962: 963: 964: 965: 966:
967: public function getIdxName($tableName, $fields, $indexType = '')
968: {
969: return Mage::getSingleton('core/resource')->getIdxName($tableName, $fields, $indexType);
970: }
971:
972: 973: 974: 975: 976: 977: 978: 979: 980:
981: public function getFkName($priTableName, $priColumnName, $refTableName, $refColumnName)
982: {
983: return Mage::getSingleton('core/resource')
984: ->getFkName($priTableName, $priColumnName, $refTableName, $refColumnName);
985: }
986:
987: 988: 989: 990: 991:
992: public function getCallAfterApplyAllUpdates()
993: {
994: return $this->_callAfterApplyAllUpdates;
995: }
996:
997: 998: 999: 1000: 1001: 1002:
1003: public function afterApplyAllUpdates()
1004: {
1005: return $this;
1006: }
1007: }
1008: