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