1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20:
21:
22: App::uses('ClassRegistry', 'Utility');
23: App::uses('Validation', 'Utility');
24: App::uses('String', 'Utility');
25: App::uses('Set', 'Utility');
26: App::uses('BehaviorCollection', 'Model');
27: App::uses('ModelBehavior', 'Model');
28: App::uses('ConnectionManager', 'Model');
29: App::uses('Xml', 'Utility');
30: App::uses('CakeEvent', 'Event');
31: App::uses('CakeEventListener', 'Event');
32: App::uses('CakeEventManager', 'Event');
33:
34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44:
45: class Model extends Object implements CakeEventListener {
46:
47: 48: 49: 50: 51: 52: 53: 54: 55:
56: public $useDbConfig = 'default';
57:
58: 59: 60: 61: 62: 63:
64: public $useTable = null;
65:
66: 67: 68: 69: 70: 71: 72: 73:
74: public $displayField = null;
75:
76: 77: 78: 79: 80: 81:
82: public $id = false;
83:
84: 85: 86: 87: 88: 89:
90: public $data = array();
91:
92: 93: 94: 95: 96: 97:
98: public $schemaName = null;
99:
100: 101: 102: 103: 104:
105: public $table = false;
106:
107: 108: 109: 110: 111: 112:
113: public $primaryKey = null;
114:
115: 116: 117: 118: 119:
120: protected $_schema = null;
121:
122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212:
213: public $validate = array();
214:
215: 216: 217: 218: 219:
220: public $validationErrors = array();
221:
222: 223: 224: 225: 226:
227: public $validationDomain = null;
228:
229: 230: 231: 232: 233: 234:
235: public $tablePrefix = null;
236:
237: 238: 239: 240: 241: 242:
243: public $name = null;
244:
245: 246: 247: 248: 249:
250: public $alias = null;
251:
252: 253: 254: 255: 256:
257: public $tableToModel = array();
258:
259: 260: 261: 262: 263: 264: 265:
266: public $cacheQueries = false;
267:
268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312:
313: public $belongsTo = array();
314:
315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355:
356: public $hasOne = array();
357:
358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404:
405: public $hasMany = array();
406:
407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461: 462: 463: 464: 465:
466: public $hasAndBelongsToMany = array();
467:
468: 469: 470: 471: 472: 473: 474: 475: 476:
477: public $actsAs = null;
478:
479: 480: 481: 482: 483:
484: public $Behaviors = null;
485:
486: 487: 488: 489: 490:
491: public $whitelist = array();
492:
493: 494: 495: 496: 497:
498: public $cacheSources = true;
499:
500: 501: 502: 503: 504:
505: public $findQueryType = null;
506:
507: 508: 509: 510: 511: 512: 513:
514: public $recursive = 1;
515:
516: 517: 518: 519: 520: 521: 522: 523: 524:
525: public $order = null;
526:
527: 528: 529: 530: 531: 532: 533: 534: 535: 536: 537: 538:
539: public $virtualFields = array();
540:
541: 542: 543: 544: 545:
546: protected $_associationKeys = array(
547: 'belongsTo' => array('className', 'foreignKey', 'conditions', 'fields', 'order', 'counterCache'),
548: 'hasOne' => array('className', 'foreignKey', 'conditions', 'fields', 'order', 'dependent'),
549: 'hasMany' => array('className', 'foreignKey', 'conditions', 'fields', 'order', 'limit', 'offset', 'dependent', 'exclusive', 'finderQuery', 'counterQuery'),
550: 'hasAndBelongsToMany' => array('className', 'joinTable', 'with', 'foreignKey', 'associationForeignKey', 'conditions', 'fields', 'order', 'limit', 'offset', 'unique', 'finderQuery', 'deleteQuery', 'insertQuery')
551: );
552:
553: 554: 555: 556: 557:
558: protected $_associations = array('belongsTo', 'hasOne', 'hasMany', 'hasAndBelongsToMany');
559:
560: 561: 562: 563: 564:
565: public $__backAssociation = array();
566:
567: 568: 569: 570: 571:
572: public $__backInnerAssociation = array();
573:
574: 575: 576: 577: 578:
579: public $__backOriginalAssociation = array();
580:
581: 582: 583: 584: 585:
586: public $__backContainableAssociation = array();
587:
588: 589: 590: 591: 592:
593: protected $_insertID = null;
594:
595: 596: 597: 598: 599: 600:
601: protected $_sourceConfigured = false;
602:
603: 604: 605: 606: 607:
608: public $findMethods = array(
609: 'all' => true, 'first' => true, 'count' => true,
610: 'neighbors' => true, 'list' => true, 'threaded' => true
611: );
612:
613: 614: 615: 616: 617: 618:
619: protected $_eventManager = null;
620:
621: 622: 623: 624: 625: 626: 627: 628: 629: 630: 631: 632: 633: 634: 635: 636: 637: 638: 639: 640: 641: 642: 643: 644: 645: 646: 647: 648: 649: 650: 651: 652:
653: public function __construct($id = false, $table = null, $ds = null) {
654: parent::__construct();
655:
656: if (is_array($id)) {
657: extract(array_merge(
658: array(
659: 'id' => $this->id, 'table' => $this->useTable, 'ds' => $this->useDbConfig,
660: 'name' => $this->name, 'alias' => $this->alias
661: ),
662: $id
663: ));
664: }
665:
666: if ($this->name === null) {
667: $this->name = (isset($name) ? $name : get_class($this));
668: }
669:
670: if ($this->alias === null) {
671: $this->alias = (isset($alias) ? $alias : $this->name);
672: }
673:
674: if ($this->primaryKey === null) {
675: $this->primaryKey = 'id';
676: }
677:
678: ClassRegistry::addObject($this->alias, $this);
679:
680: $this->id = $id;
681: unset($id);
682:
683: if ($table === false) {
684: $this->useTable = false;
685: } elseif ($table) {
686: $this->useTable = $table;
687: }
688:
689: if ($ds !== null) {
690: $this->useDbConfig = $ds;
691: }
692:
693: if (is_subclass_of($this, 'AppModel')) {
694: $merge = array('actsAs', 'findMethods');
695: $parentClass = get_parent_class($this);
696: if ($parentClass !== 'AppModel') {
697: $this->_mergeVars($merge, $parentClass);
698: }
699: $this->_mergeVars($merge, 'AppModel');
700: }
701: $this->_mergeVars(array('findMethods'), 'Model');
702:
703: $this->Behaviors = new BehaviorCollection();
704:
705: if ($this->useTable !== false) {
706:
707: if ($this->useTable === null) {
708: $this->useTable = Inflector::tableize($this->name);
709: }
710:
711: if ($this->displayField == null) {
712: unset($this->displayField);
713: }
714: $this->table = $this->useTable;
715: $this->tableToModel[$this->table] = $this->alias;
716: } elseif ($this->table === false) {
717: $this->table = Inflector::tableize($this->name);
718: }
719:
720: if ($this->tablePrefix === null) {
721: unset($this->tablePrefix);
722: }
723:
724: $this->_createLinks();
725: $this->Behaviors->init($this->alias, $this->actsAs);
726: }
727:
728: 729: 730: 731: 732: 733:
734: public function implementedEvents() {
735: return array(
736: 'Model.beforeFind' => array('callable' => 'beforeFind', 'passParams' => true),
737: 'Model.afterFind' => array('callable' => 'afterFind', 'passParams' => true),
738: 'Model.beforeValidate' => array('callable' => 'beforeValidate', 'passParams' => true),
739: 'Model.beforeSave' => array('callable' => 'beforeSave', 'passParams' => true),
740: 'Model.afterSave' => array('callable' => 'afterSave', 'passParams' => true),
741: 'Model.beforeDelete' => array('callable' => 'beforeDelete', 'passParams' => true),
742: 'Model.afterDelete' => array('callable' => 'afterDelete'),
743: );
744: }
745:
746: 747: 748: 749: 750: 751: 752:
753: public function getEventManager() {
754: if (empty($this->_eventManager)) {
755: $this->_eventManager = new CakeEventManager();
756: $this->_eventManager->attach($this->Behaviors);
757: $this->_eventManager->attach($this);
758: }
759: return $this->_eventManager;
760: }
761:
762: 763: 764: 765: 766: 767: 768: 769:
770: public function __call($method, $params) {
771: $result = $this->Behaviors->dispatchMethod($this, $method, $params);
772: if ($result !== array('unhandled')) {
773: return $result;
774: }
775: $return = $this->getDataSource()->query($method, $params, $this);
776: return $return;
777: }
778:
779: 780: 781: 782: 783: 784:
785: public function __isset($name) {
786: $className = false;
787:
788: foreach ($this->_associations as $type) {
789: if (isset($name, $this->{$type}[$name])) {
790: $className = empty($this->{$type}[$name]['className']) ? $name : $this->{$type}[$name]['className'];
791: break;
792: } elseif (isset($name, $this->__backAssociation[$type][$name])) {
793: $className = empty($this->__backAssociation[$type][$name]['className']) ?
794: $name : $this->__backAssociation[$type][$name]['className'];
795: break;
796: } elseif ($type == 'hasAndBelongsToMany') {
797: foreach ($this->{$type} as $k => $relation) {
798: if (empty($relation['with'])) {
799: continue;
800: }
801: if (is_array($relation['with'])) {
802: if (key($relation['with']) === $name) {
803: $className = $name;
804: }
805: } else {
806: list($plugin, $class) = pluginSplit($relation['with']);
807: if ($class === $name) {
808: $className = $relation['with'];
809: }
810: }
811: if ($className) {
812: $assocKey = $k;
813: $dynamic = !empty($relation['dynamicWith']);
814: break(2);
815: }
816: }
817: }
818: }
819:
820: if (!$className) {
821: return false;
822: }
823:
824: list($plugin, $className) = pluginSplit($className);
825:
826: if (!ClassRegistry::isKeySet($className) && !empty($dynamic)) {
827: $this->{$className} = new AppModel(array(
828: 'name' => $className,
829: 'table' => $this->hasAndBelongsToMany[$assocKey]['joinTable'],
830: 'ds' => $this->useDbConfig
831: ));
832: } else {
833: $this->_constructLinkedModel($name, $className, $plugin);
834: }
835:
836: if (!empty($assocKey)) {
837: $this->hasAndBelongsToMany[$assocKey]['joinTable'] = $this->{$name}->table;
838: if (count($this->{$name}->schema()) <= 2 && $this->{$name}->primaryKey !== false) {
839: $this->{$name}->primaryKey = $this->hasAndBelongsToMany[$assocKey]['foreignKey'];
840: }
841: }
842:
843: return true;
844: }
845:
846: 847: 848: 849: 850: 851:
852: public function __get($name) {
853: if ($name === 'displayField') {
854: return $this->displayField = $this->hasField(array('title', 'name', $this->primaryKey));
855: }
856: if ($name === 'tablePrefix') {
857: $this->setDataSource();
858: if (property_exists($this, 'tablePrefix') && !empty($this->tablePrefix)) {
859: return $this->tablePrefix;
860: }
861: return $this->tablePrefix = null;
862: }
863: if (isset($this->{$name})) {
864: return $this->{$name};
865: }
866: }
867:
868: 869: 870: 871: 872: 873: 874: 875: 876: 877: 878: 879: 880: 881: 882: 883: 884: 885: 886:
887: public function bindModel($params, $reset = true) {
888: foreach ($params as $assoc => $model) {
889: if ($reset === true && !isset($this->__backAssociation[$assoc])) {
890: $this->__backAssociation[$assoc] = $this->{$assoc};
891: }
892: foreach ($model as $key => $value) {
893: $assocName = $key;
894:
895: if (is_numeric($key)) {
896: $assocName = $value;
897: $value = array();
898: }
899: $this->{$assoc}[$assocName] = $value;
900: if (property_exists($this, $assocName)) {
901: unset($this->{$assocName});
902: }
903: if ($reset === false && isset($this->__backAssociation[$assoc])) {
904: $this->__backAssociation[$assoc][$assocName] = $value;
905: }
906: }
907: }
908: $this->_createLinks();
909: return true;
910: }
911:
912: 913: 914: 915: 916: 917: 918: 919: 920: 921: 922: 923: 924: 925: 926: 927: 928: 929:
930: public function unbindModel($params, $reset = true) {
931: foreach ($params as $assoc => $models) {
932: if ($reset === true && !isset($this->__backAssociation[$assoc])) {
933: $this->__backAssociation[$assoc] = $this->{$assoc};
934: }
935: foreach ($models as $model) {
936: if ($reset === false && isset($this->__backAssociation[$assoc][$model])) {
937: unset($this->__backAssociation[$assoc][$model]);
938: }
939: unset($this->{$assoc}[$model]);
940: }
941: }
942: return true;
943: }
944:
945: 946: 947: 948: 949:
950: protected function _createLinks() {
951: foreach ($this->_associations as $type) {
952: if (!is_array($this->{$type})) {
953: $this->{$type} = explode(',', $this->{$type});
954:
955: foreach ($this->{$type} as $i => $className) {
956: $className = trim($className);
957: unset ($this->{$type}[$i]);
958: $this->{$type}[$className] = array();
959: }
960: }
961:
962: if (!empty($this->{$type})) {
963: foreach ($this->{$type} as $assoc => $value) {
964: $plugin = null;
965:
966: if (is_numeric($assoc)) {
967: unset ($this->{$type}[$assoc]);
968: $assoc = $value;
969: $value = array();
970:
971: if (strpos($assoc, '.') !== false) {
972: list($plugin, $assoc) = pluginSplit($assoc);
973: $this->{$type}[$assoc] = array('className' => $plugin . '.' . $assoc);
974: } else {
975: $this->{$type}[$assoc] = $value;
976: }
977: }
978: $this->_generateAssociation($type, $assoc);
979: }
980: }
981: }
982: }
983:
984: 985: 986: 987: 988: 989: 990: 991: 992: 993: 994: 995: 996:
997: protected function _constructLinkedModel($assoc, $className = null, $plugin = null) {
998: if (empty($className)) {
999: $className = $assoc;
1000: }
1001:
1002: if (!isset($this->{$assoc}) || $this->{$assoc}->name !== $className) {
1003: if ($plugin) {
1004: $plugin .= '.';
1005: }
1006: $model = array('class' => $plugin . $className, 'alias' => $assoc);
1007: $this->{$assoc} = ClassRegistry::init($model);
1008: if ($plugin) {
1009: ClassRegistry::addObject($plugin . $className, $this->{$assoc});
1010: }
1011: if ($assoc) {
1012: $this->tableToModel[$this->{$assoc}->table] = $assoc;
1013: }
1014: }
1015: }
1016:
1017: 1018: 1019: 1020: 1021: 1022: 1023:
1024: protected function _generateAssociation($type, $assocKey) {
1025: $class = $assocKey;
1026: $dynamicWith = false;
1027:
1028: foreach ($this->_associationKeys[$type] as $key) {
1029:
1030: if (!isset($this->{$type}[$assocKey][$key]) || $this->{$type}[$assocKey][$key] === null) {
1031: $data = '';
1032:
1033: switch ($key) {
1034: case 'fields':
1035: $data = '';
1036: break;
1037:
1038: case 'foreignKey':
1039: $data = (($type == 'belongsTo') ? Inflector::underscore($assocKey) : Inflector::singularize($this->table)) . '_id';
1040: break;
1041:
1042: case 'associationForeignKey':
1043: $data = Inflector::singularize($this->{$class}->table) . '_id';
1044: break;
1045:
1046: case 'with':
1047: $data = Inflector::camelize(Inflector::singularize($this->{$type}[$assocKey]['joinTable']));
1048: $dynamicWith = true;
1049: break;
1050:
1051: case 'joinTable':
1052: $tables = array($this->table, $this->{$class}->table);
1053: sort ($tables);
1054: $data = $tables[0] . '_' . $tables[1];
1055: break;
1056:
1057: case 'className':
1058: $data = $class;
1059: break;
1060:
1061: case 'unique':
1062: $data = true;
1063: break;
1064: }
1065: $this->{$type}[$assocKey][$key] = $data;
1066: }
1067:
1068: if ($dynamicWith) {
1069: $this->{$type}[$assocKey]['dynamicWith'] = true;
1070: }
1071:
1072: }
1073: }
1074:
1075: 1076: 1077: 1078: 1079: 1080: 1081:
1082: public function setSource($tableName) {
1083: $this->setDataSource($this->useDbConfig);
1084: $db = ConnectionManager::getDataSource($this->useDbConfig);
1085: $db->cacheSources = ($this->cacheSources && $db->cacheSources);
1086:
1087: if (method_exists($db, 'listSources')) {
1088: $sources = $db->listSources();
1089: if (is_array($sources) && !in_array(strtolower($this->tablePrefix . $tableName), array_map('strtolower', $sources))) {
1090: throw new MissingTableException(array(
1091: 'table' => $this->tablePrefix . $tableName,
1092: 'class' => $this->alias,
1093: 'ds' => $this->useDbConfig,
1094: ));
1095: }
1096: $this->_schema = null;
1097: }
1098: $this->table = $this->useTable = $tableName;
1099: $this->tableToModel[$this->table] = $this->alias;
1100: }
1101:
1102: 1103: 1104: 1105: 1106: 1107: 1108: 1109: 1110: 1111: 1112: 1113: 1114: 1115: 1116:
1117: public function set($one, $two = null) {
1118: if (!$one) {
1119: return;
1120: }
1121: if (is_object($one)) {
1122: if ($one instanceof SimpleXMLElement || $one instanceof DOMNode) {
1123: $one = $this->_normalizeXmlData(Xml::toArray($one));
1124: } else {
1125: $one = Set::reverse($one);
1126: }
1127: }
1128:
1129: if (is_array($one)) {
1130: $data = $one;
1131: if (empty($one[$this->alias])) {
1132: $data = $this->_setAliasData($one);
1133: }
1134: } else {
1135: $data = array($this->alias => array($one => $two));
1136: }
1137:
1138: foreach ($data as $modelName => $fieldSet) {
1139: if (is_array($fieldSet)) {
1140:
1141: foreach ($fieldSet as $fieldName => $fieldValue) {
1142: if (isset($this->validationErrors[$fieldName])) {
1143: unset ($this->validationErrors[$fieldName]);
1144: }
1145:
1146: if ($modelName === $this->alias) {
1147: if ($fieldName === $this->primaryKey) {
1148: $this->id = $fieldValue;
1149: }
1150: }
1151: if (is_array($fieldValue) || is_object($fieldValue)) {
1152: $fieldValue = $this->deconstruct($fieldName, $fieldValue);
1153: }
1154: $this->data[$modelName][$fieldName] = $fieldValue;
1155: }
1156: }
1157: }
1158: return $data;
1159: }
1160:
1161: 1162: 1163: 1164: 1165: 1166:
1167: protected function _setAliasData($data) {
1168: $models = array_keys($this->getAssociated());
1169: $schema = array_keys((array)$this->schema());
1170: foreach ($data as $field => $value) {
1171: if (in_array($field, $schema) || !in_array($field, $models)) {
1172: $data[$this->alias][$field] = $value;
1173: unset($data[$field]);
1174: }
1175: }
1176: return $data;
1177: }
1178:
1179: 1180: 1181: 1182: 1183: 1184:
1185: protected function _normalizeXmlData(array $xml) {
1186: $return = array();
1187: foreach ($xml as $key => $value) {
1188: if (is_array($value)) {
1189: $return[Inflector::camelize($key)] = $this->_normalizeXmlData($value);
1190: } elseif ($key[0] === '@') {
1191: $return[substr($key, 1)] = $value;
1192: } else {
1193: $return[$key] = $value;
1194: }
1195: }
1196: return $return;
1197: }
1198:
1199: 1200: 1201: 1202: 1203: 1204: 1205:
1206: public function deconstruct($field, $data) {
1207: if (!is_array($data)) {
1208: return $data;
1209: }
1210:
1211: $type = $this->getColumnType($field);
1212:
1213: if (in_array($type, array('datetime', 'timestamp', 'date', 'time'))) {
1214: $useNewDate = (isset($data['year']) || isset($data['month']) ||
1215: isset($data['day']) || isset($data['hour']) || isset($data['minute']));
1216:
1217: $dateFields = array('Y' => 'year', 'm' => 'month', 'd' => 'day', 'H' => 'hour', 'i' => 'min', 's' => 'sec');
1218: $timeFields = array('H' => 'hour', 'i' => 'min', 's' => 'sec');
1219: $date = array();
1220:
1221: if (isset($data['meridian']) && empty($data['meridian'])) {
1222: return null;
1223: }
1224:
1225: if (
1226: isset($data['hour']) &&
1227: isset($data['meridian']) &&
1228: !empty($data['hour']) &&
1229: $data['hour'] != 12 &&
1230: 'pm' == $data['meridian']
1231: ) {
1232: $data['hour'] = $data['hour'] + 12;
1233: }
1234: if (isset($data['hour']) && isset($data['meridian']) && $data['hour'] == 12 && 'am' == $data['meridian']) {
1235: $data['hour'] = '00';
1236: }
1237: if ($type == 'time') {
1238: foreach ($timeFields as $key => $val) {
1239: if (!isset($data[$val]) || $data[$val] === '0' || $data[$val] === '00') {
1240: $data[$val] = '00';
1241: } elseif ($data[$val] !== '') {
1242: $data[$val] = sprintf('%02d', $data[$val]);
1243: }
1244: if (!empty($data[$val])) {
1245: $date[$key] = $data[$val];
1246: } else {
1247: return null;
1248: }
1249: }
1250: }
1251:
1252: if ($type == 'datetime' || $type == 'timestamp' || $type == 'date') {
1253: foreach ($dateFields as $key => $val) {
1254: if ($val == 'hour' || $val == 'min' || $val == 'sec') {
1255: if (!isset($data[$val]) || $data[$val] === '0' || $data[$val] === '00') {
1256: $data[$val] = '00';
1257: } else {
1258: $data[$val] = sprintf('%02d', $data[$val]);
1259: }
1260: }
1261: if (!isset($data[$val]) || isset($data[$val]) && (empty($data[$val]) || $data[$val][0] === '-')) {
1262: return null;
1263: }
1264: if (isset($data[$val]) && !empty($data[$val])) {
1265: $date[$key] = $data[$val];
1266: }
1267: }
1268: }
1269:
1270: if ($useNewDate && !empty($date)) {
1271: $format = $this->getDataSource()->columns[$type]['format'];
1272: foreach (array('m', 'd', 'H', 'i', 's') as $index) {
1273: if (isset($date[$index])) {
1274: $date[$index] = sprintf('%02d', $date[$index]);
1275: }
1276: }
1277: return str_replace(array_keys($date), array_values($date), $format);
1278: }
1279: }
1280: return $data;
1281: }
1282:
1283: 1284: 1285: 1286: 1287: 1288: 1289:
1290: public function schema($field = false) {
1291: if ($this->useTable !== false && (!is_array($this->_schema) || $field === true)) {
1292: $db = $this->getDataSource();
1293: $db->cacheSources = ($this->cacheSources && $db->cacheSources);
1294: if (method_exists($db, 'describe') && $this->useTable !== false) {
1295: $this->_schema = $db->describe($this);
1296: } elseif ($this->useTable === false) {
1297: $this->_schema = array();
1298: }
1299: }
1300: if (is_string($field)) {
1301: if (isset($this->_schema[$field])) {
1302: return $this->_schema[$field];
1303: } else {
1304: return null;
1305: }
1306: }
1307: return $this->_schema;
1308: }
1309:
1310: 1311: 1312: 1313: 1314:
1315: public function getColumnTypes() {
1316: $columns = $this->schema();
1317: if (empty($columns)) {
1318: trigger_error(__d('cake_dev', '(Model::getColumnTypes) Unable to build model field data. If you are using a model without a database table, try implementing schema()'), E_USER_WARNING);
1319: }
1320: $cols = array();
1321: foreach ($columns as $field => $values) {
1322: $cols[$field] = $values['type'];
1323: }
1324: return $cols;
1325: }
1326:
1327: 1328: 1329: 1330: 1331: 1332:
1333: public function getColumnType($column) {
1334: $db = $this->getDataSource();
1335: $cols = $this->schema();
1336: $model = null;
1337:
1338: $startQuote = isset($db->startQuote) ? $db->startQuote : null;
1339: $endQuote = isset($db->endQuote) ? $db->endQuote : null;
1340: $column = str_replace(array($startQuote, $endQuote), '', $column);
1341:
1342: if (strpos($column, '.')) {
1343: list($model, $column) = explode('.', $column);
1344: }
1345: if ($model != $this->alias && isset($this->{$model})) {
1346: return $this->{$model}->getColumnType($column);
1347: }
1348: if (isset($cols[$column]) && isset($cols[$column]['type'])) {
1349: return $cols[$column]['type'];
1350: }
1351: return null;
1352: }
1353:
1354: 1355: 1356: 1357: 1358: 1359: 1360: 1361: 1362:
1363: public function hasField($name, $checkVirtual = false) {
1364: if (is_array($name)) {
1365: foreach ($name as $n) {
1366: if ($this->hasField($n, $checkVirtual)) {
1367: return $n;
1368: }
1369: }
1370: return false;
1371: }
1372:
1373: if ($checkVirtual && !empty($this->virtualFields)) {
1374: if ($this->isVirtualField($name)) {
1375: return true;
1376: }
1377: }
1378:
1379: if (empty($this->_schema)) {
1380: $this->schema();
1381: }
1382:
1383: if ($this->_schema != null) {
1384: return isset($this->_schema[$name]);
1385: }
1386: return false;
1387: }
1388:
1389: 1390: 1391: 1392: 1393: 1394: 1395:
1396: public function hasMethod($method) {
1397: if (method_exists($this, $method)) {
1398: return true;
1399: }
1400: if ($this->Behaviors->hasMethod($method)) {
1401: return true;
1402: }
1403: return false;
1404: }
1405:
1406: 1407: 1408: 1409: 1410: 1411:
1412: public function isVirtualField($field) {
1413: if (empty($this->virtualFields) || !is_string($field)) {
1414: return false;
1415: }
1416: if (isset($this->virtualFields[$field])) {
1417: return true;
1418: }
1419: if (strpos($field, '.') !== false) {
1420: list($model, $field) = explode('.', $field);
1421: if ($model == $this->alias && isset($this->virtualFields[$field])) {
1422: return true;
1423: }
1424: }
1425: return false;
1426: }
1427:
1428: 1429: 1430: 1431: 1432: 1433: 1434: 1435:
1436: public function getVirtualField($field = null) {
1437: if ($field == null) {
1438: return empty($this->virtualFields) ? false : $this->virtualFields;
1439: }
1440: if ($this->isVirtualField($field)) {
1441: if (strpos($field, '.') !== false) {
1442: list($model, $field) = explode('.', $field);
1443: }
1444: return $this->virtualFields[$field];
1445: }
1446: return false;
1447: }
1448:
1449: 1450: 1451: 1452: 1453: 1454: 1455: 1456: 1457: 1458: 1459:
1460: public function create($data = array(), $filterKey = false) {
1461: $defaults = array();
1462: $this->id = false;
1463: $this->data = array();
1464: $this->validationErrors = array();
1465:
1466: if ($data !== null && $data !== false) {
1467: foreach ($this->schema() as $field => $properties) {
1468: if ($this->primaryKey !== $field && isset($properties['default']) && $properties['default'] !== '') {
1469: $defaults[$field] = $properties['default'];
1470: }
1471: }
1472: $this->set($defaults);
1473: $this->set($data);
1474: }
1475: if ($filterKey) {
1476: $this->set($this->primaryKey, false);
1477: }
1478: return $this->data;
1479: }
1480:
1481: 1482: 1483: 1484: 1485: 1486: 1487: 1488: 1489:
1490: public function read($fields = null, $id = null) {
1491: $this->validationErrors = array();
1492:
1493: if ($id != null) {
1494: $this->id = $id;
1495: }
1496:
1497: $id = $this->id;
1498:
1499: if (is_array($this->id)) {
1500: $id = $this->id[0];
1501: }
1502:
1503: if ($id !== null && $id !== false) {
1504: $this->data = $this->find('first', array(
1505: 'conditions' => array($this->alias . '.' . $this->primaryKey => $id),
1506: 'fields' => $fields
1507: ));
1508: return $this->data;
1509: } else {
1510: return false;
1511: }
1512: }
1513:
1514: 1515: 1516: 1517: 1518: 1519: 1520: 1521: 1522: 1523:
1524: public function field($name, $conditions = null, $order = null) {
1525: if ($conditions === null && $this->id !== false) {
1526: $conditions = array($this->alias . '.' . $this->primaryKey => $this->id);
1527: }
1528: if ($this->recursive >= 1) {
1529: $recursive = -1;
1530: } else {
1531: $recursive = $this->recursive;
1532: }
1533: $fields = $name;
1534: if ($data = $this->find('first', compact('conditions', 'fields', 'order', 'recursive'))) {
1535: if (strpos($name, '.') === false) {
1536: if (isset($data[$this->alias][$name])) {
1537: return $data[$this->alias][$name];
1538: }
1539: } else {
1540: $name = explode('.', $name);
1541: if (isset($data[$name[0]][$name[1]])) {
1542: return $data[$name[0]][$name[1]];
1543: }
1544: }
1545: if (isset($data[0]) && count($data[0]) > 0) {
1546: return array_shift($data[0]);
1547: }
1548: } else {
1549: return false;
1550: }
1551: }
1552:
1553: 1554: 1555: 1556: 1557: 1558: 1559: 1560: 1561: 1562: 1563:
1564: public function saveField($name, $value, $validate = false) {
1565: $id = $this->id;
1566: $this->create(false);
1567:
1568: if (is_array($validate)) {
1569: $options = array_merge(array('validate' => false, 'fieldList' => array($name)), $validate);
1570: } else {
1571: $options = array('validate' => $validate, 'fieldList' => array($name));
1572: }
1573: return $this->save(array($this->alias => array($this->primaryKey => $id, $name => $value)), $options);
1574: }
1575:
1576: 1577: 1578: 1579: 1580: 1581: 1582: 1583: 1584: 1585: 1586: 1587:
1588: public function save($data = null, $validate = true, $fieldList = array()) {
1589: $defaults = array('validate' => true, 'fieldList' => array(), 'callbacks' => true);
1590: $_whitelist = $this->whitelist;
1591: $fields = array();
1592:
1593: if (!is_array($validate)) {
1594: $options = array_merge($defaults, compact('validate', 'fieldList', 'callbacks'));
1595: } else {
1596: $options = array_merge($defaults, $validate);
1597: }
1598:
1599: if (!empty($options['fieldList'])) {
1600: if (!empty($options['fieldList'][$this->alias]) && is_array($options['fieldList'][$this->alias])) {
1601: $this->whitelist = $options['fieldList'][$this->alias];
1602: } else {
1603: $this->whitelist = $options['fieldList'];
1604: }
1605: } elseif ($options['fieldList'] === null) {
1606: $this->whitelist = array();
1607: }
1608: $this->set($data);
1609:
1610: if (empty($this->data) && !$this->hasField(array('created', 'updated', 'modified'))) {
1611: return false;
1612: }
1613:
1614: foreach (array('created', 'updated', 'modified') as $field) {
1615: $keyPresentAndEmpty = (
1616: isset($this->data[$this->alias]) &&
1617: array_key_exists($field, $this->data[$this->alias]) &&
1618: $this->data[$this->alias][$field] === null
1619: );
1620: if ($keyPresentAndEmpty) {
1621: unset($this->data[$this->alias][$field]);
1622: }
1623: }
1624:
1625: $exists = $this->exists();
1626: $dateFields = array('modified', 'updated');
1627:
1628: if (!$exists) {
1629: $dateFields[] = 'created';
1630: }
1631: if (isset($this->data[$this->alias])) {
1632: $fields = array_keys($this->data[$this->alias]);
1633: }
1634: if ($options['validate'] && !$this->validates($options)) {
1635: $this->whitelist = $_whitelist;
1636: return false;
1637: }
1638:
1639: $db = $this->getDataSource();
1640:
1641: foreach ($dateFields as $updateCol) {
1642: if ($this->hasField($updateCol) && !in_array($updateCol, $fields)) {
1643: $default = array('formatter' => 'date');
1644: $colType = array_merge($default, $db->columns[$this->getColumnType($updateCol)]);
1645: if (!array_key_exists('format', $colType)) {
1646: $time = strtotime('now');
1647: } else {
1648: $time = call_user_func($colType['formatter'], $colType['format']);
1649: }
1650: if (!empty($this->whitelist)) {
1651: $this->whitelist[] = $updateCol;
1652: }
1653: $this->set($updateCol, $time);
1654: }
1655: }
1656:
1657: if ($options['callbacks'] === true || $options['callbacks'] === 'before') {
1658: $event = new CakeEvent('Model.beforeSave', $this, array($options));
1659: list($event->break, $event->breakOn) = array(true, array(false, null));
1660: $this->getEventManager()->dispatch($event);
1661: if (!$event->result) {
1662: $this->whitelist = $_whitelist;
1663: return false;
1664: }
1665: }
1666:
1667: if (empty($this->data[$this->alias][$this->primaryKey])) {
1668: unset($this->data[$this->alias][$this->primaryKey]);
1669: }
1670: $fields = $values = array();
1671:
1672: foreach ($this->data as $n => $v) {
1673: if (isset($this->hasAndBelongsToMany[$n])) {
1674: if (isset($v[$n])) {
1675: $v = $v[$n];
1676: }
1677: $joined[$n] = $v;
1678: } else {
1679: if ($n === $this->alias) {
1680: foreach (array('created', 'updated', 'modified') as $field) {
1681: if (array_key_exists($field, $v) && empty($v[$field])) {
1682: unset($v[$field]);
1683: }
1684: }
1685:
1686: foreach ($v as $x => $y) {
1687: if ($this->hasField($x) && (empty($this->whitelist) || in_array($x, $this->whitelist))) {
1688: list($fields[], $values[]) = array($x, $y);
1689: }
1690: }
1691: }
1692: }
1693: }
1694: $count = count($fields);
1695:
1696: if (!$exists && $count > 0) {
1697: $this->id = false;
1698: }
1699: $success = true;
1700: $created = false;
1701:
1702: if ($count > 0) {
1703: $cache = $this->_prepareUpdateFields(array_combine($fields, $values));
1704:
1705: if (!empty($this->id)) {
1706: $success = (bool)$db->update($this, $fields, $values);
1707: } else {
1708: $fInfo = $this->schema($this->primaryKey);
1709: $isUUID = ($fInfo['length'] == 36 &&
1710: ($fInfo['type'] === 'string' || $fInfo['type'] === 'binary')
1711: );
1712: if (empty($this->data[$this->alias][$this->primaryKey]) && $isUUID) {
1713: if (array_key_exists($this->primaryKey, $this->data[$this->alias])) {
1714: $j = array_search($this->primaryKey, $fields);
1715: $values[$j] = String::uuid();
1716: } else {
1717: list($fields[], $values[]) = array($this->primaryKey, String::uuid());
1718: }
1719: }
1720:
1721: if (!$db->create($this, $fields, $values)) {
1722: $success = $created = false;
1723: } else {
1724: $created = true;
1725: }
1726: }
1727:
1728: if ($success && !empty($this->belongsTo)) {
1729: $this->updateCounterCache($cache, $created);
1730: }
1731: }
1732:
1733: if (!empty($joined) && $success === true) {
1734: $this->_saveMulti($joined, $this->id, $db);
1735: }
1736:
1737: if ($success && $count > 0) {
1738: if (!empty($this->data)) {
1739: $success = $this->data;
1740: if ($created) {
1741: $this->data[$this->alias][$this->primaryKey] = $this->id;
1742: }
1743: }
1744: if ($options['callbacks'] === true || $options['callbacks'] === 'after') {
1745: $event = new CakeEvent('Model.afterSave', $this, array($created, $options));
1746: $this->getEventManager()->dispatch($event);
1747: }
1748: if (!empty($this->data)) {
1749: $success = Set::merge($success, $this->data);
1750: }
1751: $this->data = false;
1752: $this->_clearCache();
1753: $this->validationErrors = array();
1754: }
1755: $this->whitelist = $_whitelist;
1756: return $success;
1757: }
1758:
1759: 1760: 1761: 1762: 1763: 1764: 1765: 1766:
1767: protected function _saveMulti($joined, $id, $db) {
1768: foreach ($joined as $assoc => $data) {
1769:
1770: if (isset($this->hasAndBelongsToMany[$assoc])) {
1771: list($join) = $this->joinModel($this->hasAndBelongsToMany[$assoc]['with']);
1772:
1773: $keyInfo = $this->{$join}->schema($this->{$join}->primaryKey);
1774: if ($with = $this->hasAndBelongsToMany[$assoc]['with']) {
1775: $withModel = is_array($with) ? key($with) : $with;
1776: list($pluginName, $withModel) = pluginSplit($withModel);
1777: $dbMulti = $this->{$withModel}->getDataSource();
1778: } else {
1779: $dbMulti = $db;
1780: }
1781:
1782: $isUUID = !empty($this->{$join}->primaryKey) && (
1783: $keyInfo['length'] == 36 && (
1784: $keyInfo['type'] === 'string' ||
1785: $keyInfo['type'] === 'binary'
1786: )
1787: );
1788:
1789: $newData = $newValues = $newJoins = array();
1790: $primaryAdded = false;
1791:
1792: $fields = array(
1793: $dbMulti->name($this->hasAndBelongsToMany[$assoc]['foreignKey']),
1794: $dbMulti->name($this->hasAndBelongsToMany[$assoc]['associationForeignKey'])
1795: );
1796:
1797: $idField = $db->name($this->{$join}->primaryKey);
1798: if ($isUUID && !in_array($idField, $fields)) {
1799: $fields[] = $idField;
1800: $primaryAdded = true;
1801: }
1802:
1803: foreach ((array)$data as $row) {
1804: if ((is_string($row) && (strlen($row) == 36 || strlen($row) == 16)) || is_numeric($row)) {
1805: $newJoins[] = $row;
1806: $values = array($id, $row);
1807: if ($isUUID && $primaryAdded) {
1808: $values[] = String::uuid();
1809: }
1810: $newValues[$row] = $values;
1811: unset($values);
1812: } elseif (isset($row[$this->hasAndBelongsToMany[$assoc]['associationForeignKey']])) {
1813: if (!empty($row[$this->{$join}->primaryKey])) {
1814: $newJoins[] = $row[$this->hasAndBelongsToMany[$assoc]['associationForeignKey']];
1815: }
1816: $newData[] = $row;
1817: } elseif (isset($row[$join]) && isset($row[$join][$this->hasAndBelongsToMany[$assoc]['associationForeignKey']])) {
1818: if (!empty($row[$join][$this->{$join}->primaryKey])) {
1819: $newJoins[] = $row[$join][$this->hasAndBelongsToMany[$assoc]['associationForeignKey']];
1820: }
1821: $newData[] = $row[$join];
1822: }
1823: }
1824:
1825: $keepExisting = $this->hasAndBelongsToMany[$assoc]['unique'] === 'keepExisting';
1826: if ($this->hasAndBelongsToMany[$assoc]['unique']) {
1827: $conditions = array(
1828: $join . '.' . $this->hasAndBelongsToMany[$assoc]['foreignKey'] => $id
1829: );
1830: if (!empty($this->hasAndBelongsToMany[$assoc]['conditions'])) {
1831: $conditions = array_merge($conditions, (array)$this->hasAndBelongsToMany[$assoc]['conditions']);
1832: }
1833: $associationForeignKey = $this->{$join}->alias . '.' . $this->hasAndBelongsToMany[$assoc]['associationForeignKey'];
1834: $links = $this->{$join}->find('all', array(
1835: 'conditions' => $conditions,
1836: 'recursive' => empty($this->hasAndBelongsToMany[$assoc]['conditions']) ? -1 : 0,
1837: 'fields' => $associationForeignKey,
1838: ));
1839:
1840: $oldLinks = Set::extract($links, "{n}.{$associationForeignKey}");
1841: if (!empty($oldLinks)) {
1842: if ($keepExisting && !empty($newJoins)) {
1843: $conditions[$associationForeignKey] = array_diff($oldLinks, $newJoins);
1844: } else {
1845: $conditions[$associationForeignKey] = $oldLinks;
1846: }
1847: $dbMulti->delete($this->{$join}, $conditions);
1848: }
1849: }
1850:
1851: if (!empty($newData)) {
1852: foreach ($newData as $data) {
1853: $data[$this->hasAndBelongsToMany[$assoc]['foreignKey']] = $id;
1854: if (empty($data[$this->{$join}->primaryKey])) {
1855: $this->{$join}->create();
1856: }
1857: $this->{$join}->save($data);
1858: }
1859: }
1860:
1861: if (!empty($newValues)) {
1862: if ($keepExisting && !empty($links)) {
1863: foreach ($links as $link) {
1864: $oldJoin = $link[$join][$this->hasAndBelongsToMany[$assoc]['associationForeignKey']];
1865: if (! in_array($oldJoin, $newJoins) ) {
1866: $conditions[$associationForeignKey] = $oldJoin;
1867: $db->delete($this->{$join}, $conditions);
1868: } else {
1869: unset($newValues[$oldJoin]);
1870: }
1871: }
1872: $newValues = array_values($newValues);
1873: }
1874: if (!empty($newValues)) {
1875: $dbMulti->insertMulti($this->{$join}, $fields, $newValues);
1876: }
1877: }
1878: }
1879: }
1880: }
1881:
1882: 1883: 1884: 1885: 1886: 1887: 1888: 1889:
1890: public function updateCounterCache($keys = array(), $created = false) {
1891: $keys = empty($keys) ? $this->data[$this->alias] : $keys;
1892: $keys['old'] = isset($keys['old']) ? $keys['old'] : array();
1893:
1894: foreach ($this->belongsTo as $parent => $assoc) {
1895: if (!empty($assoc['counterCache'])) {
1896: if (!is_array($assoc['counterCache'])) {
1897: if (isset($assoc['counterScope'])) {
1898: $assoc['counterCache'] = array($assoc['counterCache'] => $assoc['counterScope']);
1899: } else {
1900: $assoc['counterCache'] = array($assoc['counterCache'] => array());
1901: }
1902: }
1903:
1904: $foreignKey = $assoc['foreignKey'];
1905: $fkQuoted = $this->escapeField($assoc['foreignKey']);
1906:
1907: foreach ($assoc['counterCache'] as $field => $conditions) {
1908: if (!is_string($field)) {
1909: $field = Inflector::underscore($this->alias) . '_count';
1910: }
1911: if (!$this->{$parent}->hasField($field)) {
1912: continue;
1913: }
1914: if ($conditions === true) {
1915: $conditions = array();
1916: } else {
1917: $conditions = (array)$conditions;
1918: }
1919:
1920: if (!array_key_exists($foreignKey, $keys)) {
1921: $keys[$foreignKey] = $this->field($foreignKey);
1922: }
1923: $recursive = (empty($conditions) ? -1 : 0);
1924:
1925: if (isset($keys['old'][$foreignKey])) {
1926: if ($keys['old'][$foreignKey] != $keys[$foreignKey]) {
1927: $conditions[$fkQuoted] = $keys['old'][$foreignKey];
1928: $count = intval($this->find('count', compact('conditions', 'recursive')));
1929:
1930: $this->{$parent}->updateAll(
1931: array($field => $count),
1932: array($this->{$parent}->escapeField() => $keys['old'][$foreignKey])
1933: );
1934: }
1935: }
1936: $conditions[$fkQuoted] = $keys[$foreignKey];
1937:
1938: if ($recursive === 0) {
1939: $conditions = array_merge($conditions, (array)$conditions);
1940: }
1941: $count = intval($this->find('count', compact('conditions', 'recursive')));
1942:
1943: $this->{$parent}->updateAll(
1944: array($field => $count),
1945: array($this->{$parent}->escapeField() => $keys[$foreignKey])
1946: );
1947: }
1948: }
1949: }
1950: }
1951:
1952: 1953: 1954: 1955: 1956: 1957: 1958:
1959: protected function _prepareUpdateFields($data) {
1960: $foreignKeys = array();
1961: foreach ($this->belongsTo as $assoc => $info) {
1962: if ($info['counterCache']) {
1963: $foreignKeys[$assoc] = $info['foreignKey'];
1964: }
1965: }
1966: $included = array_intersect($foreignKeys, array_keys($data));
1967:
1968: if (empty($included) || empty($this->id)) {
1969: return array();
1970: }
1971: $old = $this->find('first', array(
1972: 'conditions' => array($this->alias . '.' . $this->primaryKey => $this->id),
1973: 'fields' => array_values($included),
1974: 'recursive' => -1
1975: ));
1976: return array_merge($data, array('old' => $old[$this->alias]));
1977: }
1978:
1979: 1980: 1981: 1982: 1983: 1984: 1985: 1986: 1987: 1988: 1989: 1990: 1991: 1992: 1993: 1994: 1995: 1996: 1997: 1998: 1999: 2000: 2001: 2002: 2003: 2004: 2005: 2006: 2007: 2008: 2009: 2010: 2011:
2012: public function saveAll($data = null, $options = array()) {
2013: $options = array_merge(array('validate' => 'first'), $options);
2014: if (Set::numeric(array_keys($data))) {
2015: if ($options['validate'] === 'only') {
2016: return $this->validateMany($data, $options);
2017: }
2018: return $this->saveMany($data, $options);
2019: }
2020: if ($options['validate'] === 'only') {
2021: return $this->validateAssociated($data, $options);
2022: }
2023: return $this->saveAssociated($data, $options);
2024: }
2025:
2026: 2027: 2028: 2029: 2030: 2031: 2032: 2033: 2034: 2035: 2036: 2037: 2038: 2039: 2040: 2041: 2042: 2043: 2044:
2045: public function saveMany($data = null, $options = array()) {
2046: if (empty($data)) {
2047: $data = $this->data;
2048: }
2049:
2050: $options = array_merge(array('validate' => 'first', 'atomic' => true, 'deep' => false), $options);
2051: $this->validationErrors = $validationErrors = array();
2052:
2053: if (empty($data) && $options['validate'] !== false) {
2054: $result = $this->save($data, $options);
2055: if (!$options['atomic']) {
2056: return array(!empty($result));
2057: }
2058: return !empty($result);
2059: }
2060:
2061: if ($options['validate'] === 'first') {
2062: $validates = $this->validateMany($data, $options);
2063: if ((!$validates && $options['atomic']) || (!$options['atomic'] && in_array(false, $validates, true))) {
2064: return $validates;
2065: }
2066: $options['validate'] = false;
2067: }
2068:
2069: if ($options['atomic']) {
2070: $db = $this->getDataSource();
2071: $transactionBegun = $db->begin();
2072: }
2073: $return = array();
2074: foreach ($data as $key => $record) {
2075: $validates = $this->create(null) !== null;
2076: $saved = false;
2077: if ($validates) {
2078: if ($options['deep']) {
2079: $saved = $this->saveAssociated($record, array_merge($options, array('atomic' => false)));
2080: } else {
2081: $saved = $this->save($record, $options);
2082: }
2083: }
2084: $validates = ($validates && ($saved === true || (is_array($saved) && !in_array(false, $saved, true))));
2085: if (!$validates) {
2086: $validationErrors[$key] = $this->validationErrors;
2087: }
2088: if (!$options['atomic']) {
2089: $return[$key] = $validates;
2090: } elseif (!$validates) {
2091: break;
2092: }
2093: }
2094: $this->validationErrors = $validationErrors;
2095:
2096: if (!$options['atomic']) {
2097: return $return;
2098: }
2099: if ($validates) {
2100: if ($transactionBegun) {
2101: return $db->commit() !== false;
2102: } else {
2103: return true;
2104: }
2105: }
2106: $db->rollback();
2107: return false;
2108: }
2109:
2110: 2111: 2112: 2113: 2114: 2115: 2116: 2117: 2118: 2119: 2120: 2121: 2122: 2123: 2124: 2125: 2126: 2127: 2128: 2129:
2130: public function validateMany(&$data, $options = array()) {
2131: $options = array_merge(array('atomic' => true, 'deep' => false), $options);
2132: $this->validationErrors = $validationErrors = $return = array();
2133: foreach ($data as $key => &$record) {
2134: if ($options['deep']) {
2135: $validates = $this->validateAssociated($record, $options);
2136: } else {
2137: $this->create(null);
2138: $validates = $this->set($record) && $this->validates($options);
2139: $data[$key] = $this->data;
2140: }
2141: if ($validates === false || (is_array($validates) && in_array(false, $validates, true))) {
2142: $validationErrors[$key] = $this->validationErrors;
2143: $validates = false;
2144: } else {
2145: $validates = true;
2146: }
2147: $return[$key] = $validates;
2148: }
2149: $this->validationErrors = $validationErrors;
2150: if (!$options['atomic']) {
2151: return $return;
2152: }
2153: if (empty($this->validationErrors)) {
2154: return true;
2155: }
2156: return false;
2157: }
2158:
2159: 2160: 2161: 2162: 2163: 2164: 2165: 2166: 2167: 2168: 2169: 2170: 2171: 2172: 2173: 2174: 2175: 2176: 2177: 2178: 2179: 2180: 2181: 2182: 2183: 2184:
2185: public function saveAssociated($data = null, $options = array()) {
2186: if (empty($data)) {
2187: $data = $this->data;
2188: }
2189:
2190: $options = array_merge(array('validate' => 'first', 'atomic' => true, 'deep' => false), $options);
2191: $this->validationErrors = $validationErrors = array();
2192:
2193: if (empty($data) && $options['validate'] !== false) {
2194: $result = $this->save($data, $options);
2195: if (!$options['atomic']) {
2196: return array(!empty($result));
2197: }
2198: return !empty($result);
2199: }
2200:
2201: if ($options['validate'] === 'first') {
2202: $validates = $this->validateAssociated($data, $options);
2203: if ((!$validates && $options['atomic']) || (!$options['atomic'] && in_array(false, $validates, true))) {
2204: return $validates;
2205: }
2206: $options['validate'] = false;
2207: }
2208: if ($options['atomic']) {
2209: $db = $this->getDataSource();
2210: $transactionBegun = $db->begin();
2211: }
2212:
2213: $associations = $this->getAssociated();
2214: $return = array();
2215: $validates = true;
2216: foreach ($data as $association => $values) {
2217: $notEmpty = !empty($values[$association]) || (!isset($values[$association]) && !empty($values));
2218: if (isset($associations[$association]) && $associations[$association] === 'belongsTo' && $notEmpty) {
2219: $validates = $this->{$association}->create(null) !== null;
2220: $saved = false;
2221: if ($validates) {
2222: if ($options['deep']) {
2223: $saved = $this->{$association}->saveAssociated($values, array_merge($options, array('atomic' => false)));
2224: } else {
2225: $saved = $this->{$association}->save($values, array_merge($options, array('atomic' => false)));
2226: }
2227: $validates = ($saved === true || (is_array($saved) && !in_array(false, $saved, true)));
2228: }
2229: if ($validates) {
2230: $key = $this->belongsTo[$association]['foreignKey'];
2231: if (isset($data[$this->alias])) {
2232: $data[$this->alias][$key] = $this->{$association}->id;
2233: } else {
2234: $data = array_merge(array($key => $this->{$association}->id), $data, array($key => $this->{$association}->id));
2235: }
2236: } else {
2237: $validationErrors[$association] = $this->{$association}->validationErrors;
2238: }
2239: $return[$association] = $validates;
2240: }
2241: }
2242: if ($validates && !($this->create(null) !== null && $this->save($data, $options))) {
2243: $validationErrors[$this->alias] = $this->validationErrors;
2244: $validates = false;
2245: }
2246: $return[$this->alias] = $validates;
2247:
2248: foreach ($data as $association => $values) {
2249: if (!$validates) {
2250: break;
2251: }
2252: $notEmpty = !empty($values[$association]) || (!isset($values[$association]) && !empty($values));
2253: if (isset($associations[$association]) && $notEmpty) {
2254: $type = $associations[$association];
2255: $key = $this->{$type}[$association]['foreignKey'];
2256: switch ($type) {
2257: case 'hasOne':
2258: if (isset($values[$association])) {
2259: $values[$association][$key] = $this->id;
2260: } else {
2261: $values = array_merge(array($key => $this->id), $values, array($key => $this->id));
2262: }
2263: $validates = $this->{$association}->create(null) !== null;
2264: $saved = false;
2265: if ($validates) {
2266: if ($options['deep']) {
2267: $saved = $this->{$association}->saveAssociated($values, array_merge($options, array('atomic' => false)));
2268: } else {
2269: $saved = $this->{$association}->save($values, $options);
2270: }
2271: }
2272: $validates = ($validates && ($saved === true || (is_array($saved) && !in_array(false, $saved, true))));
2273: if (!$validates) {
2274: $validationErrors[$association] = $this->{$association}->validationErrors;
2275: }
2276: $return[$association] = $validates;
2277: break;
2278: case 'hasMany':
2279: foreach ($values as $i => $value) {
2280: if (isset($values[$i][$association])) {
2281: $values[$i][$association][$key] = $this->id;
2282: } else {
2283: $values[$i] = array_merge(array($key => $this->id), $value, array($key => $this->id));
2284: }
2285: }
2286: $_return = $this->{$association}->saveMany($values, array_merge($options, array('atomic' => false)));
2287: if (in_array(false, $_return, true)) {
2288: $validationErrors[$association] = $this->{$association}->validationErrors;
2289: $validates = false;
2290: }
2291: $return[$association] = $_return;
2292: break;
2293: }
2294: }
2295: }
2296: $this->validationErrors = $validationErrors;
2297:
2298: if (isset($validationErrors[$this->alias])) {
2299: $this->validationErrors = $validationErrors[$this->alias];
2300: }
2301:
2302: if (!$options['atomic']) {
2303: return $return;
2304: }
2305: if ($validates) {
2306: if ($transactionBegun) {
2307: return $db->commit() !== false;
2308: } else {
2309: return true;
2310: }
2311: }
2312: $db->rollback();
2313: return false;
2314: }
2315:
2316: 2317: 2318: 2319: 2320: 2321: 2322: 2323: 2324: 2325: 2326: 2327: 2328: 2329: 2330: 2331: 2332: 2333: 2334:
2335: public function validateAssociated(&$data, $options = array()) {
2336: $options = array_merge(array('atomic' => true, 'deep' => false), $options);
2337: $this->validationErrors = $validationErrors = $return = array();
2338: $this->create(null);
2339: if (!($this->set($data) && $this->validates($options))) {
2340: $validationErrors[$this->alias] = $this->validationErrors;
2341: $return[$this->alias] = false;
2342: } else {
2343: $return[$this->alias] = true;
2344: }
2345: $data = $this->data;
2346: if (!empty($options['deep']) && isset($data[$this->alias])) {
2347: $recordData = $data[$this->alias];
2348: unset($data[$this->alias]);
2349: $data = array_merge($data, $recordData);
2350: }
2351:
2352: $associations = $this->getAssociated();
2353: foreach ($data as $association => &$values) {
2354: $validates = true;
2355: if (isset($associations[$association])) {
2356: if (in_array($associations[$association], array('belongsTo', 'hasOne'))) {
2357: if ($options['deep']) {
2358: $validates = $this->{$association}->validateAssociated($values, $options);
2359: } else {
2360: $this->{$association}->create(null);
2361: $validates = $this->{$association}->set($values) && $this->{$association}->validates($options);
2362: $data[$association] = $this->{$association}->data[$this->{$association}->alias];
2363: }
2364: if (is_array($validates)) {
2365: if (in_array(false, $validates, true)) {
2366: $validates = false;
2367: } else {
2368: $validates = true;
2369: }
2370: }
2371: $return[$association] = $validates;
2372: } elseif ($associations[$association] === 'hasMany') {
2373: $validates = $this->{$association}->validateMany($values, $options);
2374: $return[$association] = $validates;
2375: }
2376: if (!$validates || (is_array($validates) && in_array(false, $validates, true))) {
2377: $validationErrors[$association] = $this->{$association}->validationErrors;
2378: }
2379: }
2380: }
2381:
2382: $this->validationErrors = $validationErrors;
2383: if (isset($validationErrors[$this->alias])) {
2384: $this->validationErrors = $validationErrors[$this->alias];
2385: }
2386: if (!$options['atomic']) {
2387: return $return;
2388: }
2389: if ($return[$this->alias] === false || !empty($this->validationErrors)) {
2390: return false;
2391: }
2392: return true;
2393: }
2394:
2395: 2396: 2397: 2398: 2399: 2400: 2401: 2402: 2403:
2404: public function updateAll($fields, $conditions = true) {
2405: return $this->getDataSource()->update($this, $fields, null, $conditions);
2406: }
2407:
2408: 2409: 2410: 2411: 2412: 2413: 2414: 2415:
2416: public function delete($id = null, $cascade = true) {
2417: if (!empty($id)) {
2418: $this->id = $id;
2419: }
2420: $id = $this->id;
2421:
2422: $event = new CakeEvent('Model.beforeDelete', $this, array($cascade));
2423: list($event->break, $event->breakOn) = array(true, array(false, null));
2424: $this->getEventManager()->dispatch($event);
2425: if (!$event->isStopped()) {
2426: if (!$this->exists()) {
2427: return false;
2428: }
2429: $db = $this->getDataSource();
2430:
2431: $this->_deleteDependent($id, $cascade);
2432: $this->_deleteLinks($id);
2433: $this->id = $id;
2434:
2435: $updateCounterCache = false;
2436: if (!empty($this->belongsTo)) {
2437: foreach ($this->belongsTo as $parent => $assoc) {
2438: if (!empty($assoc['counterCache'])) {
2439: $updateCounterCache = true;
2440: break;
2441: }
2442: }
2443: if ($updateCounterCache) {
2444: $keys = $this->find('first', array(
2445: 'fields' => $this->_collectForeignKeys(),
2446: 'conditions' => array($this->alias . '.' . $this->primaryKey => $id),
2447: 'recursive' => -1,
2448: 'callbacks' => false
2449: ));
2450: }
2451: }
2452:
2453: if ($db->delete($this, array($this->alias . '.' . $this->primaryKey => $id))) {
2454: if ($updateCounterCache) {
2455: $this->updateCounterCache($keys[$this->alias]);
2456: }
2457: $this->getEventManager()->dispatch(new CakeEvent('Model.afterDelete', $this));
2458: $this->_clearCache();
2459: $this->id = false;
2460: return true;
2461: }
2462: }
2463: return false;
2464: }
2465:
2466: 2467: 2468: 2469: 2470: 2471: 2472:
2473: protected function _deleteDependent($id, $cascade) {
2474: if (!empty($this->__backAssociation)) {
2475: $savedAssociatons = $this->__backAssociation;
2476: $this->__backAssociation = array();
2477: }
2478: if ($cascade === true) {
2479: foreach (array_merge($this->hasMany, $this->hasOne) as $assoc => $data) {
2480: if ($data['dependent'] === true) {
2481:
2482: $model = $this->{$assoc};
2483:
2484: if ($data['foreignKey'] === false && $data['conditions'] && in_array($this->name, $model->getAssociated('belongsTo'))) {
2485: $model->recursive = 0;
2486: $conditions = array($this->escapeField(null, $this->name) => $id);
2487: } else {
2488: $model->recursive = -1;
2489: $conditions = array($model->escapeField($data['foreignKey']) => $id);
2490: if ($data['conditions']) {
2491: $conditions = array_merge((array)$data['conditions'], $conditions);
2492: }
2493: }
2494:
2495: if (isset($data['exclusive']) && $data['exclusive']) {
2496: $model->deleteAll($conditions);
2497: } else {
2498: $records = $model->find('all', array(
2499: 'conditions' => $conditions, 'fields' => $model->primaryKey
2500: ));
2501:
2502: if (!empty($records)) {
2503: foreach ($records as $record) {
2504: $model->delete($record[$model->alias][$model->primaryKey]);
2505: }
2506: }
2507: }
2508: }
2509: }
2510: }
2511: if (isset($savedAssociatons)) {
2512: $this->__backAssociation = $savedAssociatons;
2513: }
2514: }
2515:
2516: 2517: 2518: 2519: 2520: 2521:
2522: protected function _deleteLinks($id) {
2523: foreach ($this->hasAndBelongsToMany as $assoc => $data) {
2524: list($plugin, $joinModel) = pluginSplit($data['with']);
2525: $records = $this->{$joinModel}->find('all', array(
2526: 'conditions' => array($this->{$joinModel}->escapeField($data['foreignKey']) => $id),
2527: 'fields' => $this->{$joinModel}->primaryKey,
2528: 'recursive' => -1,
2529: 'callbacks' => false
2530: ));
2531: if (!empty($records)) {
2532: foreach ($records as $record) {
2533: $this->{$joinModel}->delete($record[$this->{$joinModel}->alias][$this->{$joinModel}->primaryKey]);
2534: }
2535: }
2536: }
2537: }
2538:
2539: 2540: 2541: 2542: 2543: 2544: 2545: 2546: 2547:
2548: public function deleteAll($conditions, $cascade = true, $callbacks = false) {
2549: if (empty($conditions)) {
2550: return false;
2551: }
2552: $db = $this->getDataSource();
2553:
2554: if (!$cascade && !$callbacks) {
2555: return $db->delete($this, $conditions);
2556: } else {
2557: $ids = $this->find('all', array_merge(array(
2558: 'fields' => "{$this->alias}.{$this->primaryKey}",
2559: 'recursive' => 0), compact('conditions'))
2560: );
2561: if ($ids === false) {
2562: return false;
2563: }
2564:
2565: $ids = Set::extract($ids, "{n}.{$this->alias}.{$this->primaryKey}");
2566: if (empty($ids)) {
2567: return true;
2568: }
2569:
2570: if ($callbacks) {
2571: $_id = $this->id;
2572: $result = true;
2573: foreach ($ids as $id) {
2574: $result = ($result && $this->delete($id, $cascade));
2575: }
2576: $this->id = $_id;
2577: return $result;
2578: } else {
2579: foreach ($ids as $id) {
2580: $this->_deleteLinks($id);
2581: if ($cascade) {
2582: $this->_deleteDependent($id, $cascade);
2583: }
2584: }
2585: return $db->delete($this, array($this->alias . '.' . $this->primaryKey => $ids));
2586: }
2587: }
2588: }
2589:
2590: 2591: 2592: 2593: 2594: 2595:
2596: protected function _collectForeignKeys($type = 'belongsTo') {
2597: $result = array();
2598:
2599: foreach ($this->{$type} as $assoc => $data) {
2600: if (isset($data['foreignKey']) && is_string($data['foreignKey'])) {
2601: $result[$assoc] = $data['foreignKey'];
2602: }
2603: }
2604: return $result;
2605: }
2606:
2607: 2608: 2609: 2610: 2611: 2612: 2613: 2614: 2615: 2616:
2617: public function exists($id = null) {
2618: if ($id === null) {
2619: $id = $this->getID();
2620: }
2621: if ($id === false) {
2622: return false;
2623: }
2624: $conditions = array($this->alias . '.' . $this->primaryKey => $id);
2625: $query = array('conditions' => $conditions, 'recursive' => -1, 'callbacks' => false);
2626: return ($this->find('count', $query) > 0);
2627: }
2628:
2629: 2630: 2631: 2632: 2633: 2634:
2635: public function hasAny($conditions = null) {
2636: return ($this->find('count', array('conditions' => $conditions, 'recursive' => -1)) != false);
2637: }
2638:
2639: 2640: 2641: 2642: 2643: 2644: 2645: 2646: 2647: 2648: 2649: 2650: 2651: 2652: 2653: 2654: 2655: 2656: 2657: 2658: 2659: 2660: 2661: 2662: 2663: 2664: 2665: 2666: 2667: 2668: 2669: 2670: 2671: 2672: 2673: 2674: 2675: 2676: 2677: 2678: 2679: 2680: 2681: 2682: 2683: 2684: 2685: 2686: 2687: 2688: 2689: 2690: 2691:
2692: public function find($type = 'first', $query = array()) {
2693: $this->findQueryType = $type;
2694: $this->id = $this->getID();
2695:
2696: $query = $this->buildQuery($type, $query);
2697: if (is_null($query)) {
2698: return null;
2699: }
2700:
2701: $results = $this->getDataSource()->read($this, $query);
2702: $this->resetAssociations();
2703:
2704: if ($query['callbacks'] === true || $query['callbacks'] === 'after') {
2705: $results = $this->_filterResults($results);
2706: }
2707:
2708: $this->findQueryType = null;
2709:
2710: if ($type === 'all') {
2711: return $results;
2712: } else {
2713: if ($this->findMethods[$type] === true) {
2714: return $this->{'_find' . ucfirst($type)}('after', $query, $results);
2715: }
2716: }
2717: }
2718:
2719: 2720: 2721: 2722: 2723: 2724: 2725: 2726:
2727: public function buildQuery($type = 'first', $query = array()) {
2728: $query = array_merge(
2729: array(
2730: 'conditions' => null, 'fields' => null, 'joins' => array(), 'limit' => null,
2731: 'offset' => null, 'order' => null, 'page' => 1, 'group' => null, 'callbacks' => true,
2732: ),
2733: (array)$query
2734: );
2735:
2736: if ($type !== 'all') {
2737: if ($this->findMethods[$type] === true) {
2738: $query = $this->{'_find' . ucfirst($type)}('before', $query);
2739: }
2740: }
2741:
2742: if (!is_numeric($query['page']) || intval($query['page']) < 1) {
2743: $query['page'] = 1;
2744: }
2745: if ($query['page'] > 1 && !empty($query['limit'])) {
2746: $query['offset'] = ($query['page'] - 1) * $query['limit'];
2747: }
2748: if ($query['order'] === null && $this->order !== null) {
2749: $query['order'] = $this->order;
2750: }
2751: $query['order'] = array($query['order']);
2752:
2753: if ($query['callbacks'] === true || $query['callbacks'] === 'before') {
2754: $event = new CakeEvent('Model.beforeFind', $this, array($query));
2755: list($event->break, $event->breakOn, $event->modParams) = array(true, array(false, null), 0);
2756: $this->getEventManager()->dispatch($event);
2757: if ($event->isStopped()) {
2758: return null;
2759: }
2760: $query = $event->result === true ? $event->data[0] : $event->result;
2761: }
2762:
2763: return $query;
2764: }
2765:
2766: 2767: 2768: 2769: 2770: 2771: 2772: 2773: 2774:
2775: protected function _findFirst($state, $query, $results = array()) {
2776: if ($state === 'before') {
2777: $query['limit'] = 1;
2778: return $query;
2779: } elseif ($state === 'after') {
2780: if (empty($results[0])) {
2781: return false;
2782: }
2783: return $results[0];
2784: }
2785: }
2786:
2787: 2788: 2789: 2790: 2791: 2792: 2793: 2794: 2795:
2796: protected function _findCount($state, $query, $results = array()) {
2797: if ($state === 'before') {
2798: $db = $this->getDataSource();
2799: $query['order'] = false;
2800: if (!method_exists($db, 'calculate')) {
2801: return $query;
2802: }
2803: if (empty($query['fields'])) {
2804: $query['fields'] = $db->calculate($this, 'count');
2805: } elseif (method_exists($db, 'expression') && is_string($query['fields']) && !preg_match('/count/i', $query['fields'])) {
2806: $query['fields'] = $db->calculate($this, 'count', array(
2807: $db->expression($query['fields']), 'count'
2808: ));
2809: }
2810: return $query;
2811: } elseif ($state === 'after') {
2812: foreach (array(0, $this->alias) as $key) {
2813: if (isset($results[0][$key]['count'])) {
2814: if (($count = count($results)) > 1) {
2815: return $count;
2816: } else {
2817: return intval($results[0][$key]['count']);
2818: }
2819: }
2820: }
2821: return false;
2822: }
2823: }
2824:
2825: 2826: 2827: 2828: 2829: 2830: 2831: 2832: 2833:
2834: protected function _findList($state, $query, $results = array()) {
2835: if ($state === 'before') {
2836: if (empty($query['fields'])) {
2837: $query['fields'] = array("{$this->alias}.{$this->primaryKey}", "{$this->alias}.{$this->displayField}");
2838: $list = array("{n}.{$this->alias}.{$this->primaryKey}", "{n}.{$this->alias}.{$this->displayField}", null);
2839: } else {
2840: if (!is_array($query['fields'])) {
2841: $query['fields'] = String::tokenize($query['fields']);
2842: }
2843:
2844: if (count($query['fields']) === 1) {
2845: if (strpos($query['fields'][0], '.') === false) {
2846: $query['fields'][0] = $this->alias . '.' . $query['fields'][0];
2847: }
2848:
2849: $list = array("{n}.{$this->alias}.{$this->primaryKey}", '{n}.' . $query['fields'][0], null);
2850: $query['fields'] = array("{$this->alias}.{$this->primaryKey}", $query['fields'][0]);
2851: } elseif (count($query['fields']) === 3) {
2852: for ($i = 0; $i < 3; $i++) {
2853: if (strpos($query['fields'][$i], '.') === false) {
2854: $query['fields'][$i] = $this->alias . '.' . $query['fields'][$i];
2855: }
2856: }
2857:
2858: $list = array('{n}.' . $query['fields'][0], '{n}.' . $query['fields'][1], '{n}.' . $query['fields'][2]);
2859: } else {
2860: for ($i = 0; $i < 2; $i++) {
2861: if (strpos($query['fields'][$i], '.') === false) {
2862: $query['fields'][$i] = $this->alias . '.' . $query['fields'][$i];
2863: }
2864: }
2865:
2866: $list = array('{n}.' . $query['fields'][0], '{n}.' . $query['fields'][1], null);
2867: }
2868: }
2869: if (!isset($query['recursive']) || $query['recursive'] === null) {
2870: $query['recursive'] = -1;
2871: }
2872: list($query['list']['keyPath'], $query['list']['valuePath'], $query['list']['groupPath']) = $list;
2873: return $query;
2874: } elseif ($state === 'after') {
2875: if (empty($results)) {
2876: return array();
2877: }
2878: $lst = $query['list'];
2879: return Set::combine($results, $lst['keyPath'], $lst['valuePath'], $lst['groupPath']);
2880: }
2881: }
2882:
2883: 2884: 2885: 2886: 2887: 2888: 2889: 2890: 2891:
2892: protected function _findNeighbors($state, $query, $results = array()) {
2893: if ($state === 'before') {
2894: extract($query);
2895: $conditions = (array)$conditions;
2896: if (isset($field) && isset($value)) {
2897: if (strpos($field, '.') === false) {
2898: $field = $this->alias . '.' . $field;
2899: }
2900: } else {
2901: $field = $this->alias . '.' . $this->primaryKey;
2902: $value = $this->id;
2903: }
2904: $query['conditions'] = array_merge($conditions, array($field . ' <' => $value));
2905: $query['order'] = $field . ' DESC';
2906: $query['limit'] = 1;
2907: $query['field'] = $field;
2908: $query['value'] = $value;
2909: return $query;
2910: } elseif ($state === 'after') {
2911: extract($query);
2912: unset($query['conditions'][$field . ' <']);
2913: $return = array();
2914: if (isset($results[0])) {
2915: $prevVal = Set::extract('/' . str_replace('.', '/', $field), $results[0]);
2916: $query['conditions'][$field . ' >='] = $prevVal[0];
2917: $query['conditions'][$field . ' !='] = $value;
2918: $query['limit'] = 2;
2919: } else {
2920: $return['prev'] = null;
2921: $query['conditions'][$field . ' >'] = $value;
2922: $query['limit'] = 1;
2923: }
2924: $query['order'] = $field . ' ASC';
2925: $return2 = $this->find('all', $query);
2926: if (!array_key_exists('prev', $return)) {
2927: $return['prev'] = $return2[0];
2928: }
2929: if (count($return2) === 2) {
2930: $return['next'] = $return2[1];
2931: } elseif (count($return2) === 1 && !$return['prev']) {
2932: $return['next'] = $return2[0];
2933: } else {
2934: $return['next'] = null;
2935: }
2936: return $return;
2937: }
2938: }
2939:
2940: 2941: 2942: 2943: 2944: 2945: 2946: 2947: 2948:
2949: protected function _findThreaded($state, $query, $results = array()) {
2950: if ($state === 'before') {
2951: return $query;
2952: } elseif ($state === 'after') {
2953: $parent = 'parent_id';
2954: if (isset($query['parent'])) {
2955: $parent = $query['parent'];
2956: }
2957: return Set::nest($results, array(
2958: 'idPath' => '/' . $this->alias . '/' . $this->primaryKey,
2959: 'parentPath' => '/' . $this->alias . '/' . $parent
2960: ));
2961: }
2962: }
2963:
2964: 2965: 2966: 2967: 2968: 2969: 2970:
2971: protected function _filterResults($results, $primary = true) {
2972: $event = new CakeEvent('Model.afterFind', $this, array($results, $primary));
2973: $event->modParams = 0;
2974: $this->getEventManager()->dispatch($event);
2975: return $event->result;
2976: }
2977:
2978: 2979: 2980: 2981: 2982: 2983: 2984:
2985: public function resetAssociations() {
2986: if (!empty($this->__backAssociation)) {
2987: foreach ($this->_associations as $type) {
2988: if (isset($this->__backAssociation[$type])) {
2989: $this->{$type} = $this->__backAssociation[$type];
2990: }
2991: }
2992: $this->__backAssociation = array();
2993: }
2994:
2995: foreach ($this->_associations as $type) {
2996: foreach ($this->{$type} as $key => $name) {
2997: if (property_exists($this, $key) && !empty($this->{$key}->__backAssociation)) {
2998: $this->{$key}->resetAssociations();
2999: }
3000: }
3001: }
3002: $this->__backAssociation = array();
3003: return true;
3004: }
3005:
3006: 3007: 3008: 3009: 3010: 3011: 3012:
3013: public function isUnique($fields, $or = true) {
3014: if (!is_array($fields)) {
3015: $fields = func_get_args();
3016: if (is_bool($fields[count($fields) - 1])) {
3017: $or = $fields[count($fields) - 1];
3018: unset($fields[count($fields) - 1]);
3019: }
3020: }
3021:
3022: foreach ($fields as $field => $value) {
3023: if (is_numeric($field)) {
3024: unset($fields[$field]);
3025:
3026: $field = $value;
3027: if (isset($this->data[$this->alias][$field])) {
3028: $value = $this->data[$this->alias][$field];
3029: } else {
3030: $value = null;
3031: }
3032: }
3033:
3034: if (strpos($field, '.') === false) {
3035: unset($fields[$field]);
3036: $fields[$this->alias . '.' . $field] = $value;
3037: }
3038: }
3039: if ($or) {
3040: $fields = array('or' => $fields);
3041: }
3042: if (!empty($this->id)) {
3043: $fields[$this->alias . '.' . $this->primaryKey . ' !='] = $this->id;
3044: }
3045: return ($this->find('count', array('conditions' => $fields, 'recursive' => -1)) == 0);
3046: }
3047:
3048: 3049: 3050: 3051: 3052: 3053: 3054:
3055: public function query($sql) {
3056: $params = func_get_args();
3057: $db = $this->getDataSource();
3058: return call_user_func_array(array(&$db, 'query'), $params);
3059: }
3060:
3061: 3062: 3063: 3064: 3065: 3066: 3067: 3068: 3069:
3070: public function validates($options = array()) {
3071: $errors = $this->invalidFields($options);
3072: if (empty($errors) && $errors !== false) {
3073: $errors = $this->_validateWithModels($options);
3074: }
3075: if (is_array($errors)) {
3076: return count($errors) === 0;
3077: }
3078: return $errors;
3079: }
3080:
3081: 3082: 3083: 3084: 3085: 3086: 3087:
3088: public function invalidFields($options = array()) {
3089: $event = new CakeEvent('Model.beforeValidate', $this, array($options));
3090: list($event->break, $event->breakOn) = array(true, false);
3091: $this->getEventManager()->dispatch($event);
3092: if ($event->isStopped()) {
3093: return false;
3094: }
3095:
3096: if (!isset($this->validate) || empty($this->validate)) {
3097: return $this->validationErrors;
3098: }
3099:
3100: $data = $this->data;
3101: $methods = array_map('strtolower', get_class_methods($this));
3102: $behaviorMethods = array_keys($this->Behaviors->methods());
3103:
3104: if (isset($data[$this->alias])) {
3105: $data = $data[$this->alias];
3106: } elseif (!is_array($data)) {
3107: $data = array();
3108: }
3109:
3110: $exists = null;
3111:
3112: $_validate = $this->validate;
3113: $whitelist = $this->whitelist;
3114:
3115: if (!empty($options['fieldList'])) {
3116: if (!empty($options['fieldList'][$this->alias]) && is_array($options['fieldList'][$this->alias])) {
3117: $whitelist = $options['fieldList'][$this->alias];
3118: } else {
3119: $whitelist = $options['fieldList'];
3120: }
3121: }
3122:
3123: if (!empty($whitelist)) {
3124: $validate = array();
3125: foreach ((array)$whitelist as $f) {
3126: if (!empty($this->validate[$f])) {
3127: $validate[$f] = $this->validate[$f];
3128: }
3129: }
3130: $this->validate = $validate;
3131: }
3132:
3133: $validationDomain = $this->validationDomain;
3134: if (empty($validationDomain)) {
3135: $validationDomain = 'default';
3136: }
3137:
3138: foreach ($this->validate as $fieldName => $ruleSet) {
3139: if (!is_array($ruleSet) || (is_array($ruleSet) && isset($ruleSet['rule']))) {
3140: $ruleSet = array($ruleSet);
3141: }
3142: $default = array(
3143: 'allowEmpty' => null,
3144: 'required' => null,
3145: 'rule' => 'blank',
3146: 'last' => true,
3147: 'on' => null
3148: );
3149:
3150: foreach ($ruleSet as $index => $validator) {
3151: if (!is_array($validator)) {
3152: $validator = array('rule' => $validator);
3153: }
3154: $validator = array_merge($default, $validator);
3155:
3156: if (!empty($validator['on']) || in_array($validator['required'], array('create', 'update'), true)) {
3157: if ($exists === null) {
3158: $exists = $this->exists();
3159: }
3160: if ($validator['on'] == 'create' && $exists || $validator['on'] == 'update' && !$exists) {
3161: continue;
3162: }
3163: if ($validator['required'] === 'create' && !$exists || $validator['required'] === 'update' && $exists) {
3164: $validator['required'] = true;
3165: }
3166: }
3167:
3168: $valid = true;
3169: $requiredFail = (
3170: (!isset($data[$fieldName]) && $validator['required'] === true) ||
3171: (
3172: isset($data[$fieldName]) && (empty($data[$fieldName]) &&
3173: !is_numeric($data[$fieldName])) && $validator['allowEmpty'] === false
3174: )
3175: );
3176:
3177: if (!$requiredFail && array_key_exists($fieldName, $data)) {
3178: if (empty($data[$fieldName]) && $data[$fieldName] != '0' && $validator['allowEmpty'] === true) {
3179: break;
3180: }
3181: if (is_array($validator['rule'])) {
3182: $rule = $validator['rule'][0];
3183: unset($validator['rule'][0]);
3184: $ruleParams = array_merge(array($data[$fieldName]), array_values($validator['rule']));
3185: } else {
3186: $rule = $validator['rule'];
3187: $ruleParams = array($data[$fieldName]);
3188: }
3189:
3190: if (in_array(strtolower($rule), $methods)) {
3191: $ruleParams[] = $validator;
3192: $ruleParams[0] = array($fieldName => $ruleParams[0]);
3193: $valid = $this->dispatchMethod($rule, $ruleParams);
3194: } elseif (in_array($rule, $behaviorMethods) || in_array(strtolower($rule), $behaviorMethods)) {
3195: $ruleParams[] = $validator;
3196: $ruleParams[0] = array($fieldName => $ruleParams[0]);
3197: $valid = $this->Behaviors->dispatchMethod($this, $rule, $ruleParams);
3198: } elseif (method_exists('Validation', $rule)) {
3199: $valid = call_user_func_array(array('Validation', $rule), $ruleParams);
3200: } elseif (!is_array($validator['rule'])) {
3201: $valid = preg_match($rule, $data[$fieldName]);
3202: } elseif (Configure::read('debug') > 0) {
3203: trigger_error(__d('cake_dev', 'Could not find validation handler %s for %s', $rule, $fieldName), E_USER_WARNING);
3204: }
3205: }
3206:
3207: if ($requiredFail || !$valid || (is_string($valid) && strlen($valid) > 0)) {
3208: if (is_string($valid)) {
3209: $message = $valid;
3210: } elseif (isset($validator['message'])) {
3211: $args = null;
3212: if (is_array($validator['message'])) {
3213: $message = $validator['message'][0];
3214: $args = array_slice($validator['message'], 1);
3215: } else {
3216: $message = $validator['message'];
3217: }
3218: if (is_array($validator['rule']) && $args === null) {
3219: $args = array_slice($ruleSet[$index]['rule'], 1);
3220: }
3221: $message = __d($validationDomain, $message, $args);
3222: } elseif (is_string($index)) {
3223: if (is_array($validator['rule'])) {
3224: $args = array_slice($ruleSet[$index]['rule'], 1);
3225: $message = __d($validationDomain, $index, $args);
3226: } else {
3227: $message = __d($validationDomain, $index);
3228: }
3229: } elseif (!$requiredFail && is_numeric($index) && count($ruleSet) > 1) {
3230: $message = $index + 1;
3231: } else {
3232: $message = __d('cake_dev', 'This field cannot be left blank');
3233: }
3234:
3235: $this->invalidate($fieldName, $message);
3236: if ($validator['last']) {
3237: break;
3238: }
3239: }
3240: }
3241: }
3242: $this->validate = $_validate;
3243: return $this->validationErrors;
3244: }
3245:
3246: 3247: 3248: 3249: 3250: 3251: 3252: 3253:
3254: protected function _validateWithModels($options) {
3255: $valid = true;
3256: foreach ($this->hasAndBelongsToMany as $assoc => $association) {
3257: if (empty($association['with']) || !isset($this->data[$assoc])) {
3258: continue;
3259: }
3260: list($join) = $this->joinModel($this->hasAndBelongsToMany[$assoc]['with']);
3261: $data = $this->data[$assoc];
3262:
3263: $newData = array();
3264: foreach ((array)$data as $row) {
3265: if (isset($row[$this->hasAndBelongsToMany[$assoc]['associationForeignKey']])) {
3266: $newData[] = $row;
3267: } elseif (isset($row[$join]) && isset($row[$join][$this->hasAndBelongsToMany[$assoc]['associationForeignKey']])) {
3268: $newData[] = $row[$join];
3269: }
3270: }
3271: if (empty($newData)) {
3272: continue;
3273: }
3274: foreach ($newData as $data) {
3275: $data[$this->hasAndBelongsToMany[$assoc]['foreignKey']] = $this->id;
3276: $this->{$join}->create($data);
3277: $valid = ($valid && $this->{$join}->validates($options));
3278: }
3279: }
3280: return $valid;
3281: }
3282:
3283: 3284: 3285: 3286: 3287: 3288: 3289: 3290: 3291:
3292: public function invalidate($field, $value = true) {
3293: if (!is_array($this->validationErrors)) {
3294: $this->validationErrors = array();
3295: }
3296: $this->validationErrors[$field][] = $value;
3297: }
3298:
3299: 3300: 3301: 3302: 3303: 3304:
3305: public function isForeignKey($field) {
3306: $foreignKeys = array();
3307: if (!empty($this->belongsTo)) {
3308: foreach ($this->belongsTo as $assoc => $data) {
3309: $foreignKeys[] = $data['foreignKey'];
3310: }
3311: }
3312: return in_array($field, $foreignKeys);
3313: }
3314:
3315: 3316: 3317: 3318: 3319: 3320: 3321: 3322:
3323: public function escapeField($field = null, $alias = null) {
3324: if (empty($alias)) {
3325: $alias = $this->alias;
3326: }
3327: if (empty($field)) {
3328: $field = $this->primaryKey;
3329: }
3330: $db = $this->getDataSource();
3331: if (strpos($field, $db->name($alias) . '.') === 0) {
3332: return $field;
3333: }
3334: return $db->name($alias . '.' . $field);
3335: }
3336:
3337: 3338: 3339: 3340: 3341: 3342:
3343: public function getID($list = 0) {
3344: if (empty($this->id) || (is_array($this->id) && isset($this->id[0]) && empty($this->id[0]))) {
3345: return false;
3346: }
3347:
3348: if (!is_array($this->id)) {
3349: return $this->id;
3350: }
3351:
3352: if (isset($this->id[$list]) && !empty($this->id[$list])) {
3353: return $this->id[$list];
3354: } elseif (isset($this->id[$list])) {
3355: return false;
3356: }
3357:
3358: return current($this->id);
3359: }
3360:
3361: 3362: 3363: 3364: 3365:
3366: public function getLastInsertID() {
3367: return $this->getInsertID();
3368: }
3369:
3370: 3371: 3372: 3373: 3374:
3375: public function getInsertID() {
3376: return $this->_insertID;
3377: }
3378:
3379: 3380: 3381: 3382: 3383: 3384:
3385: public function setInsertID($id) {
3386: $this->_insertID = $id;
3387: }
3388:
3389: 3390: 3391: 3392: 3393:
3394: public function getNumRows() {
3395: return $this->getDataSource()->lastNumRows();
3396: }
3397:
3398: 3399: 3400: 3401: 3402:
3403: public function getAffectedRows() {
3404: return $this->getDataSource()->lastAffected();
3405: }
3406:
3407: 3408: 3409: 3410: 3411: 3412: 3413:
3414: public function setDataSource($dataSource = null) {
3415: $oldConfig = $this->useDbConfig;
3416:
3417: if ($dataSource != null) {
3418: $this->useDbConfig = $dataSource;
3419: }
3420: $db = ConnectionManager::getDataSource($this->useDbConfig);
3421: if (!empty($oldConfig) && isset($db->config['prefix'])) {
3422: $oldDb = ConnectionManager::getDataSource($oldConfig);
3423:
3424: if (!isset($this->tablePrefix) || (!isset($oldDb->config['prefix']) || $this->tablePrefix == $oldDb->config['prefix'])) {
3425: $this->tablePrefix = $db->config['prefix'];
3426: }
3427: } elseif (isset($db->config['prefix'])) {
3428: $this->tablePrefix = $db->config['prefix'];
3429: }
3430:
3431: $this->schemaName = $db->getSchemaName();
3432: }
3433:
3434: 3435: 3436: 3437: 3438:
3439: public function getDataSource() {
3440: if (!$this->_sourceConfigured && $this->useTable !== false) {
3441: $this->_sourceConfigured = true;
3442: $this->setSource($this->useTable);
3443: }
3444: return ConnectionManager::getDataSource($this->useDbConfig);
3445: }
3446:
3447: 3448: 3449: 3450: 3451:
3452: public function associations() {
3453: return $this->_associations;
3454: }
3455:
3456: 3457: 3458: 3459: 3460: 3461:
3462: public function getAssociated($type = null) {
3463: if ($type == null) {
3464: $associated = array();
3465: foreach ($this->_associations as $assoc) {
3466: if (!empty($this->{$assoc})) {
3467: $models = array_keys($this->{$assoc});
3468: foreach ($models as $m) {
3469: $associated[$m] = $assoc;
3470: }
3471: }
3472: }
3473: return $associated;
3474: } elseif (in_array($type, $this->_associations)) {
3475: if (empty($this->{$type})) {
3476: return array();
3477: }
3478: return array_keys($this->{$type});
3479: } else {
3480: $assoc = array_merge(
3481: $this->hasOne,
3482: $this->hasMany,
3483: $this->belongsTo,
3484: $this->hasAndBelongsToMany
3485: );
3486: if (array_key_exists($type, $assoc)) {
3487: foreach ($this->_associations as $a) {
3488: if (isset($this->{$a}[$type])) {
3489: $assoc[$type]['association'] = $a;
3490: break;
3491: }
3492: }
3493: return $assoc[$type];
3494: }
3495: return null;
3496: }
3497: }
3498:
3499: 3500: 3501: 3502: 3503: 3504: 3505: 3506:
3507: public function joinModel($assoc, $keys = array()) {
3508: if (is_string($assoc)) {
3509: list(, $assoc) = pluginSplit($assoc);
3510: return array($assoc, array_keys($this->{$assoc}->schema()));
3511: } elseif (is_array($assoc)) {
3512: $with = key($assoc);
3513: return array($with, array_unique(array_merge($assoc[$with], $keys)));
3514: }
3515: trigger_error(
3516: __d('cake_dev', 'Invalid join model settings in %s', $model->alias),
3517: E_USER_WARNING
3518: );
3519: }
3520:
3521: 3522: 3523: 3524: 3525: 3526: 3527: 3528: 3529:
3530: public function beforeFind($queryData) {
3531: return true;
3532: }
3533:
3534: 3535: 3536: 3537: 3538: 3539: 3540: 3541: 3542:
3543: public function afterFind($results, $primary = false) {
3544: return $results;
3545: }
3546:
3547: 3548: 3549: 3550: 3551: 3552: 3553: 3554:
3555: public function beforeSave($options = array()) {
3556: return true;
3557: }
3558:
3559: 3560: 3561: 3562: 3563: 3564: 3565:
3566: public function afterSave($created) {
3567: }
3568:
3569: 3570: 3571: 3572: 3573: 3574: 3575:
3576: public function beforeDelete($cascade = true) {
3577: return true;
3578: }
3579:
3580: 3581: 3582: 3583: 3584: 3585:
3586: public function afterDelete() {
3587: }
3588:
3589: 3590: 3591: 3592: 3593: 3594: 3595: 3596:
3597: public function beforeValidate($options = array()) {
3598: return true;
3599: }
3600:
3601: 3602: 3603: 3604: 3605: 3606:
3607: public function onError() {
3608: }
3609:
3610: 3611: 3612: 3613: 3614: 3615: 3616:
3617: protected function _clearCache($type = null) {
3618: if ($type === null) {
3619: if (Configure::read('Cache.check') === true) {
3620: $assoc[] = strtolower(Inflector::pluralize($this->alias));
3621: $assoc[] = strtolower(Inflector::underscore(Inflector::pluralize($this->alias)));
3622: foreach ($this->_associations as $key => $association) {
3623: foreach ($this->$association as $key => $className) {
3624: $check = strtolower(Inflector::pluralize($className['className']));
3625: if (!in_array($check, $assoc)) {
3626: $assoc[] = strtolower(Inflector::pluralize($className['className']));
3627: $assoc[] = strtolower(Inflector::underscore(Inflector::pluralize($className['className'])));
3628: }
3629: }
3630: }
3631: clearCache($assoc);
3632: return true;
3633: }
3634: } else {
3635:
3636: }
3637: }
3638:
3639: }
3640: