Subversion Repositories SmartDukaan

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
14098 anikendra 1
<?php
2
/**
3
 * A CakePHP datasource for the mongoDB (http://www.mongodb.org/) document-oriented database.
4
 *
5
 * This datasource uses Pecl Mongo (http://php.net/mongo)
6
 * and is thus dependent on PHP 5.0 and greater.
7
 *
8
 * Original implementation by ichikaway(Yasushi Ichikawa) http://github.com/ichikaway/
9
 *
10
 * Reference:
11
 *	Nate Abele's lithium mongoDB datasource (http://li3.rad-dev.org/)
12
 *	Joél Perras' divan(http://github.com/jperras/divan/)
13
 *
14
 * Copyright 2010, Yasushi Ichikawa http://github.com/ichikaway/
15
 *
16
 * Contributors: Predominant, Jrbasso, tkyk, AD7six
17
 *
18
 * Licensed under The MIT License
19
 * Redistributions of files must retain the above copyright notice.
20
 *
21
 * @copyright     Copyright 2010, Yasushi Ichikawa http://github.com/ichikaway/
22
 * @package       mongodb
23
 * @subpackage    mongodb.models.datasources
24
 * @license       http://www.opensource.org/licenses/mit-license.php The MIT License
25
 */
26
 
27
App::uses('DboSource', 'Model/Datasource');
28
App::uses('SchemalessBehavior', 'Mongodb.Model/Behavior');
29
 
30
/**
31
 * MongoDB Source
32
 *
33
 * @package       mongodb
34
 * @subpackage    mongodb.models.datasources
35
 */
36
class MongodbSource extends DboSource {
37
 
38
/**
39
 * Are we connected to the DataSource?
40
 *
41
 * true - yes
42
 * null - haven't tried yet
43
 * false - nope, and we can't connect
44
 *
45
 * @var boolean
46
 * @access public
47
 */
48
	public $connected = null;
49
 
50
/**
51
 * Database Instance
52
 *
53
 * @var resource
54
 * @access protected
55
 */
56
	protected $_db = null;
57
 
58
/**
59
 * Mongo Driver Version
60
 *
61
 * @var string
62
 * @access protected
63
 */
64
	protected $_driverVersion = Mongo::VERSION;
65
 
66
/**
67
 * startTime property
68
 *
69
 * If debugging is enabled, stores the (micro)time the current query started
70
 *
71
 * @var mixed null
72
 * @access protected
73
 */
74
	protected $_startTime = null;
75
 
76
/**
77
 * Direct connection with database, isn't the
78
 * same of DboSource::_connection
79
 *
80
 * @var mixed null | Mongo
81
 * @access private
82
 */
83
	public $connection = null;
84
 
85
/**
86
 * Base Config
87
 *
88
 * set_string_id:
89
 *    true: In read() method, convert MongoId object to string and set it to array 'id'.
90
 *    false: not convert and set.
91
 *
92
 * @var array
93
 * @access public
94
 *
95
 */
96
	public $_baseConfig = array(
97
		'set_string_id' => true,
98
		'persistent' => true,
99
		'host'       => 'localhost',
100
		'database'   => '',
101
		'port'       => '27017',
102
		'login'		=> '',
103
		'password'	=> '',
104
		'replicaset'	=> '',
105
	);
106
 
107
/**
108
 * column definition
109
 *
110
 * @var array
111
 */
112
	public $columns = array(
113
		'boolean' => array('name' => 'boolean'),
114
		'string' => array('name' => 'varchar'),
115
		'text' => array('name' => 'text'),
116
		'integer' => array('name' => 'integer', 'format' => null, 'formatter' => 'intval'),
117
		'float' => array('name' => 'float', 'format' => null, 'formatter' => 'floatval'),
118
		'datetime' => array('name' => 'datetime', 'format' => null, 'formatter' => 'MongodbDateFormatter'),
119
		'timestamp' => array('name' => 'timestamp', 'format' => null, 'formatter' => 'MongodbDateFormatter'),
120
		'time' => array('name' => 'time', 'format' => null, 'formatter' => 'MongodbDateFormatter'),
121
		'date' => array('name' => 'date', 'format' => null, 'formatter' => 'MongodbDateFormatter'),
122
	);
123
 
124
/**
125
 * Default schema for the mongo models
126
 *
127
 * @var array
128
 * @access protected
129
 */
130
	protected $_defaultSchema = array(
131
		'_id' => array('type' => 'string', 'length' => 24, 'key' => 'primary'),
132
		'created' => array('type' => 'datetime', 'default' => null),
133
		'modified' => array('type' => 'datetime', 'default' => null)
134
	);
135
 
136
/**
137
 * construct method
138
 *
139
 * By default don't try to connect until you need to
140
 *
141
 * @param array $config Configuration array
142
 * @param bool $autoConnect false
143
 * @return void
144
 * @access public
145
 */
146
	function __construct($config = array(), $autoConnect = false) {
147
		return parent::__construct($config, $autoConnect);
148
	}
149
 
150
/**
151
 * Destruct
152
 *
153
 * @access public
154
 */
155
	public function __destruct() {
156
		if ($this->connected) {
157
			$this->disconnect();
158
		}
159
	}
160
 
161
/**
162
 * commit method
163
 *
164
 * MongoDB doesn't support transactions
165
 *
166
 * @return void
167
 * @access public
168
 */
169
	public function commit() {
170
		return false;
171
	}
172
 
173
/**
174
 * Connect to the database
175
 *
176
 * If using 1.0.2 or above use the mongodb:// format to connect
177
 * The connect syntax changed in version 1.0.2 - so check for that too
178
 *
179
 * If authentication information in present then authenticate the connection
180
 *
181
 * @return boolean Connected
182
 * @access public
183
 */
184
	public function connect() {
185
		$this->connected = false;
186
 
187
		try{
188
 
189
			$host = $this->createConnectionName($this->config, $this->_driverVersion);
190
			$class = 'MongoClient';
191
			if(!class_exists($class)){
192
				$class = 'Mongo';
193
			}
194
 
195
			if (isset($this->config['replicaset']) && count($this->config['replicaset']) === 2) {
196
				$this->connection = new $class($this->config['replicaset']['host'], $this->config['replicaset']['options']);
197
			} else if ($this->_driverVersion >= '1.3.0') {
198
				$this->connection = new $class($host);
199
			} else if ($this->_driverVersion >= '1.2.0') {
200
				$this->connection = new $class($host, array("persist" => $this->config['persistent']));
201
			} else {
202
				$this->connection = new $class($host, true, $this->config['persistent']);
203
			}
204
 
205
			if (isset($this->config['slaveok'])) {
206
				if (method_exists($this->connection, 'setSlaveOkay')) {
207
					$this->connection->setSlaveOkay($this->config['slaveok']);
208
				} else {
209
					$this->connection->setReadPreference($this->config['slaveok']
210
						? $class::RP_SECONDARY_PREFERRED : $class::RP_PRIMARY);
211
				}
212
			}
213
 
214
			if ($this->_db = $this->connection->selectDB($this->config['database'])) {
215
				if (!empty($this->config['login']) && $this->_driverVersion < '1.2.0') {
216
					$return = $this->_db->authenticate($this->config['login'], $this->config['password']);
217
					if (!$return || !$return['ok']) {
218
						trigger_error('MongodbSource::connect ' . $return['errmsg']);
219
						return false;
220
					}
221
				}
222
				$this->connected = true;
223
			}
224
 
225
		} catch(MongoException $e) {
226
			$this->error = $e->getMessage();
227
			trigger_error($this->error);
228
		}
229
		return $this->connected;
230
	}
231
 
232
/**
233
 * create connection name.
234
 *
235
 * @param array $config
236
 * @param string $version  version of MongoDriver
237
 */
238
		public function createConnectionName($config, $version) {
239
			$host = null;
240
 
241
			if ($version >= '1.0.2') {
242
				$host = "mongodb://";
243
			} else {
244
				$host = '';
245
			}
246
			$hostname = $config['host'] . ':' . $config['port'];
247
 
248
			if(!empty($config['login'])){
249
				$host .= $config['login'] .':'. $config['password'] . '@' . $hostname . '/'. $config['database'];
250
			} else {
251
				$host .= $hostname;
252
			}
253
 
254
			return $host;
255
		}
256
 
257
 
258
/**
259
 * Inserts multiple values into a table
260
 *
261
 * @param string $table
262
 * @param string $fields
263
 * @param array $values
264
 * @access public
265
 */
266
	public function insertMulti($table, $fields, $values) {
267
		$table = $this->fullTableName($table);
268
 
269
		if (!is_array($fields) || !is_array($values)) {
270
			return false;
271
		}
272
 
273
		$inUse = array_search('id', $fields);
274
		$default = array_search('_id', $fields);
275
 
276
		if ($inUse !== false && $default === false) {
277
			$fields[$inUse] = '_id';
278
		}
279
 
280
		$values = $this->normalizeValues($table, $fields, $values);
281
 
282
		$data = array();
283
		foreach ($values as $row) {
284
			if (is_string($row)) {
285
				$row = explode(', ', substr($row, 1, -1));
286
			}
287
			$data[] = array_combine($fields, $row);
288
		}
289
 
290
		$this->_prepareLogQuery($table); // just sets a timer
291
		try{
292
			$return = $this->_db
293
				->selectCollection($table)
294
				->batchInsert($data, array('w' => 1));
295
		} catch (MongoException $e) {
296
			$this->error = $e->getMessage();
297
			trigger_error($this->error);
298
		}
299
		if ($this->fullDebug) {
300
			$this->logQuery("db.{$table}.insertMulti( :data , array('w' => 1))", compact('data'));
301
		}
302
	}
303
 
304
	public function normalizeValues($table, $fields, $values) {
305
		$Model = ClassRegistry::init(Inflector::classify($table));
306
 
307
		foreach ($values as $key => $value) {
308
			foreach ($value as $k => $v) {
309
				switch($Model->mongoSchema[$fields[$k]]['type']) {
310
					case 'datetime':
311
					case 'timestamp':
312
					case 'date':
313
					case 'time':
314
						if (is_string($values[$key][$k])) {
315
							$values[$key][$k] = new MongoDate(strtotime($v));
316
						}
317
						break;
318
					default:
319
						break;
320
				}
321
			}
322
		}
323
 
324
		return $values;
325
	}
326
 
327
/**
328
 * check connection to the database
329
 *
330
 * @return boolean Connected
331
 * @access public
332
 */
333
	public function isConnected() {
334
		if ($this->connected === false) {
335
			return false;
336
		}
337
		return $this->connect();
338
	}
339
 
340
/**
341
 * get MongoDB Object
342
 *
343
 * @return mixed MongoDB Object
344
 * @access public
345
 */
346
	public function getMongoDb() {
347
		if ($this->connected === false) {
348
			return false;
349
		}
350
		return $this->_db;
351
	}
352
 
353
/**
354
 * get MongoDB Collection Object
355
 *
356
 * @return mixed MongoDB Collection Object
357
 * @access public
358
 */
359
	public function getMongoCollection(&$Model) {
360
		if ($this->connected === false) {
361
			return false;
362
		}
363
 
364
        $table = $this->fullTableName($Model);
365
 
366
		$collection = $this->_db
367
			->selectCollection($table);
368
		return $collection;
369
	}
370
 
371
/**
372
 * isInterfaceSupported method
373
 *
374
 * listSources is infact supported, however: cake expects it to return a complete list of all
375
 * possible sources in the selected db - the possible list of collections is infinte, so it's
376
 * faster and simpler to tell cake that the interface is /not/ supported so it assumes that
377
 * <insert name of your table here> exist
378
 *
379
 * @param mixed $interface
380
 * @return void
381
 * @access public
382
 */
383
	public function isInterfaceSupported($interface) {
384
		if ($interface === 'listSources') {
385
			return false;
386
		}
387
		return parent::isInterfaceSupported($interface);
388
	}
389
 
390
/**
391
 * Close database connection
392
 *
393
 * @return boolean Connected
394
 * @access public
395
 */
396
	public function close() {
397
		return $this->disconnect();
398
	}
399
 
400
/**
401
 * Disconnect from the database
402
 *
403
 * @return boolean Connected
404
 * @access public
405
 */
406
	public function disconnect() {
407
		if ($this->connected) {
408
			$this->connected = !$this->connection->close();
409
			unset($this->_db, $this->connection);
410
			return !$this->connected;
411
		}
412
		return true;
413
	}
414
 
415
/**
416
 * Get list of available Collections
417
 *
418
 * @param array $data
419
 * @return array Collections
420
 * @access public
421
 */
422
	public function listSources($data = null) {
423
		if (!$this->isConnected()) {
424
			return false;
425
		}
426
		return true;
427
	}
428
 
429
/**
430
 * Describe
431
 *
432
 * Automatically bind the schemaless behavior if there is no explicit mongo schema.
433
 * When called, if there is model data it will be used to derive a schema. a row is plucked
434
 * out of the db and the data obtained used to derive the schema.
435
 *
436
 * @param Model $Model
437
 * @return array if model instance has mongoSchema, return it.
438
 * @access public
439
 */
440
	public function describe($Model) {
441
		if(empty($Model->primaryKey)) {
442
			$Model->primaryKey = '_id';
443
		}
444
 
445
		$schema = array();
446
        $table = $this->fullTableName($Model);
447
 
448
		if (!empty($Model->mongoSchema) && is_array($Model->mongoSchema)) {
449
			$schema = $Model->mongoSchema;
450
			return $schema + array($Model->primaryKey => $this->_defaultSchema['_id']);
451
		} elseif ($this->isConnected() && is_a($Model, 'Model') && !empty($Model->Behaviors)) {
452
			$Model->Behaviors->attach('Mongodb.Schemaless');
453
			if (!$Model->data) {
454
				if ($this->_db->selectCollection($table)->count()) {
455
					return $this->deriveSchemaFromData($Model, $this->_db->selectCollection($table)->findOne());
456
				}
457
			}
458
		}
459
		return $this->deriveSchemaFromData($Model);
460
	}
461
 
462
/**
463
 * begin method
464
 *
465
 * Mongo doesn't support transactions
466
 *
467
 * @return void
468
 * @access public
469
 */
470
	public function begin() {
471
		return false;
472
	}
473
 
474
/**
475
 * Calculate
476
 *
477
 * @param Model $Model
478
 * @return array
479
 * @access public
480
 */
481
	public function calculate(Model $Model, $func, $params = array()) {
482
		return array('count' => true);
483
	}
484
 
485
/**
486
 * Quotes identifiers.
487
 *
488
 * MongoDb does not need identifiers quoted, so this method simply returns the identifier.
489
 *
490
 * @param string $name The identifier to quote.
491
 * @return string The quoted identifier.
492
 */
493
	public function name($name) {
494
		return $name;
495
	}
496
 
497
/**
498
 * Create Data
499
 *
500
 * @param Model $Model Model Instance
501
 * @param array $fields Field data
502
 * @param array $values Save data
503
 * @return boolean Insert result
504
 * @access public
505
 */
506
	public function create(Model $Model, $fields = null, $values = null) {
507
		if (!$this->isConnected()) {
508
			return false;
509
		}
510
 
511
		if ($fields !== null && $values !== null) {
512
			$data = array_combine($fields, $values);
513
		} else {
514
			$data = $Model->data;
515
		}
516
 
517
		if($Model->primaryKey !== '_id' && isset($data[$Model->primaryKey]) && !empty($data[$Model->primaryKey])) {
518
			$data['_id'] = $data[$Model->primaryKey];
519
			unset($data[$Model->primaryKey]);
520
		}
521
 
522
		if (!empty($data['_id'])) {
523
			$this->_convertId($data['_id']);
524
		}
525
 
526
		$this->_prepareLogQuery($Model); // just sets a timer
527
        $table = $this->fullTableName($Model);
528
		try{
529
			if ($this->_driverVersion >= '1.3.0') {
530
				$return = $this->_db
531
					->selectCollection($table)
532
					->insert($data, array('safe' => true));
533
			} else {
534
				$return = $this->_db
535
					->selectCollection($table)
536
					->insert($data, true);
537
			}
538
		} catch (MongoException $e) {
539
			$this->error = $e->getMessage();
540
			trigger_error($this->error);
541
		}
542
		if ($this->fullDebug) {
543
			$this->logQuery("db.{$table}.insert( :data , true)", compact('data'));
544
		}
545
 
546
		if (!empty($return) && $return['ok']) {
547
 
548
			$id = $data['_id'];
549
			if($this->config['set_string_id'] && is_object($data['_id'])) {
550
				$id = $data['_id']->__toString();
551
			}
552
			$Model->setInsertID($id);
553
			$Model->id = $id;
554
			return true;
555
		}
556
		return false;
557
	}
558
 
559
/**
560
 * createSchema method
561
 *
562
 * Mongo no care for creating schema. Mongo work with no schema.
563
 *
564
 * @param mixed $schema
565
 * @param mixed $tableName null
566
 * @return void
567
 * @access public
568
 */
569
	public function createSchema($schema, $tableName = null) {
570
		return true;
571
	}
572
 
573
/**
574
 * dropSchema method
575
 *
576
 * Return a command to drop each table
577
 *
578
 * @param mixed $schema
579
 * @param mixed $tableName null
580
 * @return void
581
 * @access public
582
 */
583
	public function dropSchema(CakeSchema $schema, $tableName = null) {
584
		if (!$this->isConnected()) {
585
			return false;
586
		}
587
 
588
		if (!is_a($schema, 'CakeSchema')) {
589
			trigger_error(__('Invalid schema object', true), E_USER_WARNING);
590
			return null;
591
		}
592
		if ($tableName) {
593
			return "db.{$tableName}.drop();";
594
		}
595
 
596
		$toDrop = array();
597
		foreach ($schema->tables as $curTable => $columns) {
598
			if ($tableName === $curTable) {
599
				$toDrop[] = $curTable;
600
			}
601
		}
602
 
603
		if (count($toDrop) === 1) {
604
			return "db.{$toDrop[0]}.drop();";
605
		}
606
 
607
		$return = "toDrop = :tables;\nfor( i = 0; i < toDrop.length; i++ ) {\n\tdb[toDrop[i]].drop();\n}";
608
		$tables = '["' . implode($toDrop, '", "') . '"]';
609
 
610
		return String::insert($return, compact('tables'));
611
	}
612
 
613
/**
614
 * distinct method
615
 *
616
 * @param mixed $Model
617
 * @param array $keys array()
618
 * @param array $params array()
619
 * @return void
620
 * @access public
621
 */
622
	public function distinct(&$Model, $keys = array(), $params = array()) {
623
		if (!$this->isConnected()) {
624
			return false;
625
		}
626
 
627
		$this->_prepareLogQuery($Model); // just sets a timer
628
 
629
		if (array_key_exists('conditions', $params)) {
630
			$params = $params['conditions'];
631
		}
632
 
633
        $table = $this->fullTableName($Model);
634
 
635
		try{
636
			$return = $this->_db
637
				->selectCollection($table)
638
				->distinct($keys, $params);
639
		} catch (MongoException $e) {
640
			$this->error = $e->getMessage();
641
			trigger_error($this->error);
642
		}
643
		if ($this->fullDebug) {
644
			$this->logQuery("db.{$table}.distinct( :keys, :params )", compact('keys', 'params'));
645
		}
646
 
647
		return $return;
648
	}
649
 
650
 
651
/**
652
 * group method
653
 *
654
 * @param array $params array()
655
 *   Set params  same as MongoCollection::group()
656
 *    key,initial, reduce, options(conditions, finalize)
657
 *
658
 *   Ex. $params = array(
659
 *           'key' => array('field' => true),
660
 *           'initial' => array('csum' => 0),
661
 *           'reduce' => 'function(obj, prev){prev.csum += 1;}',
662
 *           'options' => array(
663
 *                'condition' => array('age' => array('$gt' => 20)),
664
 *                'finalize' => array(),
665
 *           ),
666
 *       );
667
 * @param mixed $Model
668
 * @return void
669
 * @access public
670
 */
671
	public function group($params, Model $Model = null) {
672
 
673
		if (!$this->isConnected() || count($params) === 0 || $Model === null) {
674
			return false;
675
		}
676
 
677
		$this->_prepareLogQuery($Model); // just sets a timer
678
 
679
		$key = (empty($params['key'])) ? array() : $params['key'];
680
		$initial = (empty($params['initial'])) ? array() : $params['initial'];
681
		$reduce = (empty($params['reduce'])) ? array() : $params['reduce'];
682
		$options = (empty($params['options'])) ? array() : $params['options'];
683
        $table = $this->fullTableName($Model);
684
 
685
		try{
686
			$return = $this->_db
687
				->selectCollection($table)
688
				->group($key, $initial, $reduce, $options);
689
		} catch (MongoException $e) {
690
			$this->error = $e->getMessage();
691
			trigger_error($this->error);
692
		}
693
		if ($this->fullDebug) {
694
			$this->logQuery("db.{$table}.group( :key, :initial, :reduce, :options )", $params);
695
		}
696
 
697
		return $return;
698
	}
699
 
700
 
701
/**
702
 * ensureIndex method
703
 *
704
 * @param mixed $Model
705
 * @param array $keys array()
706
 * @param array $params array()
707
 * @return void
708
 * @access public
709
 */
710
	public function ensureIndex(&$Model, $keys = array(), $params = array()) {
711
		if (!$this->isConnected()) {
712
			return false;
713
		}
714
 
715
		$this->_prepareLogQuery($Model); // just sets a timer
716
        $table = $this->fullTableName($Model);
717
 
718
		try{
719
			$return = $this->_db
720
				->selectCollection($table)
721
				->ensureIndex($keys, $params);
722
		} catch (MongoException $e) {
723
			$this->error = $e->getMessage();
724
			trigger_error($this->error);
725
		}
726
		if ($this->fullDebug) {
727
			$this->logQuery("db.{$table}.ensureIndex( :keys, :params )", compact('keys', 'params'));
728
		}
729
 
730
		return $return;
731
	}
732
 
733
/**
734
 * Update Data
735
 *
736
 * This method uses $set operator automatically with MongoCollection::update().
737
 * If you don't want to use $set operator, you can chose any one as follw.
738
 *  1. Set TRUE in Model::mongoNoSetOperator property.
739
 *  2. Set a mongodb operator in a key of save data as follow.
740
 *      Model->save(array('_id' => $id, '$inc' => array('count' => 1)));
741
 *      Don't use Model::mongoSchema property,
742
 *       CakePHP delete '$inc' data in Model::Save().
743
 *  3. Set a Mongo operator in Model::mongoNoSetOperator property.
744
 *      Model->mongoNoSetOperator = '$inc';
745
 *      Model->save(array('_id' => $id, array('count' => 1)));
746
 *
747
 * @param Model $Model Model Instance
748
 * @param array $fields Field data
749
 * @param array $values Save data
750
 * @return boolean Update result
751
 * @access public
752
 */
753
	public function update(Model $Model, $fields = null, $values = null, $conditions = null) {
754
		if (!$this->isConnected()) {
755
			return false;
756
		}
757
 
758
		if ($fields !== null && $values !== null) {
759
			$data = array_combine($fields, $values);
760
		} elseif($fields !== null && $conditions !== null) {
761
			return $this->updateAll($Model, $fields, $conditions);
762
		} else{
763
			$data = $Model->data;
764
		}
765
 
766
		if($Model->primaryKey !== '_id' && isset($data[$Model->primaryKey]) && !empty($data[$Model->primaryKey])) {
767
			$data['_id'] = $data[$Model->primaryKey];
768
			unset($data[$Model->primaryKey]);
769
		}
770
 
771
		if (empty($data['_id'])) {
772
			$data['_id'] = $Model->id;
773
		}
774
 
775
		$this->_convertId($data['_id']);
776
        $table = $this->fullTableName($Model);
777
 
778
		try{
779
			$mongoCollectionObj = $this->_db
780
				->selectCollection($table);
781
		} catch (MongoException $e) {
782
			$this->error = $e->getMessage();
783
			trigger_error($this->error);
784
			return false;
785
		}
786
 
787
		$this->_prepareLogQuery($Model); // just sets a timer
788
		if (!empty($data['_id'])) {
789
			$this->_convertId($data['_id']);
790
			$cond = array('_id' => $data['_id']);
791
			unset($data['_id']);
792
 
793
			$data = $this->setMongoUpdateOperator($Model, $data);
794
 
795
			try{
796
				if ($this->_driverVersion >= '1.3.0') {
797
					$return = $mongoCollectionObj->update($cond, $data, array("multiple" => false, 'safe' => true));
798
				} else {
799
					$return = $mongoCollectionObj->update($cond, $data, array("multiple" => false));
800
				}
801
			} catch (MongoException $e) {
802
				$this->error = $e->getMessage();
803
				trigger_error($this->error);
804
			}
805
			if ($this->fullDebug) {
806
				$this->logQuery("db.{$table}.update( :conditions, :data, :params )",
807
					array('conditions' => $cond, 'data' => $data, 'params' => array("multiple" => false))
808
				);
809
			}
810
		} else {
811
			try{
812
				if ($this->_driverVersion >= '1.3.0') {
813
					$return = $mongoCollectionObj->save($data, array('safe' => true));
814
				} else {
815
					$return = $mongoCollectionObj->save($data);
816
				}
817
			} catch (MongoException $e) {
818
				$this->error = $e->getMessage();
819
				trigger_error($this->error);
820
			}
821
			if ($this->fullDebug) {
822
				$this->logQuery("db.{$table}.save( :data )", compact('data'));
823
			}
824
		}
825
		return $return;
826
	}
827
 
828
 
829
/**
830
 * setMongoUpdateOperator
831
 *
832
 * Set Mongo update operator following saving data.
833
 * This method is for update() and updateAll.
834
 *
835
 * @param Model $Model Model Instance
836
 * @param array $values Save data
837
 * @return array $data
838
 * @access public
839
 */
840
	public function setMongoUpdateOperator(&$Model, $data) {
841
		if(isset($data['updated'])) {
842
			$updateField = 'updated';
843
		} else {
844
			$updateField = 'modified';
845
		}
846
 
847
		//setting Mongo operator
848
		if(empty($Model->mongoNoSetOperator)) {
849
			if(!preg_grep('/^\$/', array_keys($data))) {
850
				$data = array('$set' => $data);
851
			} else {
852
				if(!empty($data[$updateField])) {
853
					$modified = $data[$updateField];
854
					unset($data[$updateField]);
855
					$data['$set'] = array($updateField => $modified);
856
				}
857
			}
858
		} elseif(substr($Model->mongoNoSetOperator,0,1) === '$') {
859
			if(!empty($data[$updateField])) {
860
				$modified = $data[$updateField];
861
				unset($data[$updateField]);
862
				$data = array($Model->mongoNoSetOperator => $data, '$set' => array($updateField => $modified));
863
			} else {
864
				$data = array($Model->mongoNoSetOperator => $data);
865
 
866
			}
867
		}
868
 
869
		return $data;
870
	}
871
 
872
/**
873
 * Update multiple Record
874
 *
875
 * @param Model $Model Model Instance
876
 * @param array $fields Field data
877
 * @param array $conditions
878
 * @return boolean Update result
879
 * @access public
880
 */
881
	public function updateAll(&$Model, $fields = null,  $conditions = null) {
882
		if (!$this->isConnected()) {
883
			return false;
884
		}
885
 
886
		$this->_stripAlias($conditions, $Model->alias);
887
		$this->_stripAlias($fields, $Model->alias, false, 'value');
888
 
889
		$fields = $this->setMongoUpdateOperator($Model, $fields);
890
 
891
		$this->_prepareLogQuery($Model); // just sets a timer
892
        $table = $this->fullTableName($Model);
893
		try{
894
			if ($this->_driverVersion >= '1.3.0') {
895
				// not use 'upsert'
896
				$return = $this->_db
897
					->selectCollection($table)
898
					->update($conditions, $fields, array("multiple" => true, 'safe' => true));
899
				if (isset($return['updatedExisting'])) {
900
					$return = $return['updatedExisting'];
901
				}
902
			} else {
903
				$return = $this->_db
904
					->selectCollection($table)
905
					->update($conditions, $fields, array("multiple" => true));
906
			}
907
		} catch (MongoException $e) {
908
			$this->error = $e->getMessage();
909
			trigger_error($this->error);
910
		}
911
 
912
		if ($this->fullDebug) {
913
			$this->logQuery("db.{$table}.update( :conditions, :fields, :params )",
914
				array('conditions' => $conditions, 'fields' => $fields, 'params' => array("multiple" => true))
915
			);
916
		}
917
		return $return;
918
	}
919
 
920
/**
921
 * deriveSchemaFromData method
922
 *
923
 * @param mixed $Model
924
 * @param array $data array()
925
 * @return void
926
 * @access public
927
 */
928
	public function deriveSchemaFromData($Model, $data = array()) {
929
		if (!$data) {
930
			$data = $Model->data;
931
			if ($data && array_key_exists($Model->alias, $data)) {
932
				$data = $data[$Model->alias];
933
			}
934
		}
935
 
936
		$return = $this->_defaultSchema;
937
 
938
		if ($data) {
939
			$fields = array_keys($data);
940
			foreach($fields as $field) {
941
				if (in_array($field, array('created', 'modified', 'updated'))) {
942
					$return[$field] = array('type' => 'datetime', 'null' => true);
943
				} else {
944
					$return[$field] = array('type' => 'string', 'length' => 2000);
945
				}
946
			}
947
		}
948
 
949
		return $return;
950
	}
951
 
952
/**
953
 * Delete Data
954
 *
955
 * For deleteAll(true, false) calls - conditions will arrive here as true - account for that and
956
 * convert to an empty array
957
 * For deleteAll(array('some conditions')) calls - conditions will arrive here as:
958
 *  array(
959
 *  	Alias._id => array(1, 2, 3, ...)
960
 *  )
961
 *
962
 * This format won't be understood by mongodb, it'll find 0 rows. convert to:
963
 *
964
 *  array(
965
 *  	Alias._id => array('$in' => array(1, 2, 3, ...))
966
 *  )
967
 *
968
 * @TODO bench remove() v drop. if it's faster to drop - just drop the collection taking into
969
 *  	account existing indexes (recreate just the indexes)
970
 * @param Model $Model Model Instance
971
 * @param array $conditions
972
 * @return boolean Update result
973
 * @access public
974
 */
975
	public function delete(Model $Model, $conditions = null) {
976
		if (!$this->isConnected()) {
977
			return false;
978
		}
979
 
980
		$id = null;
981
 
982
		$this->_stripAlias($conditions, $Model->alias);
983
 
984
		if ($conditions === true) {
985
			$conditions = array();
986
		} elseif (empty($conditions)) {
987
			$id = $Model->id;
988
		} elseif (!empty($conditions) && !is_array($conditions)) {
989
			$id = $conditions;
990
			$conditions = array();
991
		} elseif (!empty($conditions['id'])) { //for cakephp2.0
992
			$id = $conditions['id'];
993
			unset($conditions['id']);
994
		}
995
 
996
        $table = $this->fullTableName($Model);
997
 
998
		$mongoCollectionObj = $this->_db
999
			->selectCollection($table);
1000
 
1001
		$this->_stripAlias($conditions, $Model->alias);
1002
		if (!empty($id)) {
1003
			$conditions['_id'] = $id;
1004
		}
1005
		if (!empty($conditions['_id'])) {
1006
			$this->_convertId($conditions['_id'], true);
1007
		}
1008
 
1009
		$return = false;
1010
		$r = false;
1011
		try{
1012
			$this->_prepareLogQuery($Model); // just sets a timer
1013
			$return = $mongoCollectionObj->remove($conditions);
1014
			if ($this->fullDebug) {
1015
				$this->logQuery("db.{$table}.remove( :conditions )",
1016
					compact('conditions')
1017
				);
1018
			}
1019
			$return = true;
1020
		} catch (MongoException $e) {
1021
			$this->error = $e->getMessage();
1022
			trigger_error($this->error);
1023
		}
1024
		return $return;
1025
	}
1026
 
1027
/**
1028
 * Read Data
1029
 *
1030
 * For deleteAll(true) calls - the conditions will arrive here as true - account for that and switch to an empty array
1031
 *
1032
 * @param Model $Model Model Instance
1033
 * @param array $query Query data
1034
 * @param mixed  $recursive
1035
 * @return array Results
1036
 * @access public
1037
 */
1038
	public function read(Model $Model, $query = array(), $recursive = null) {
1039
		if (!$this->isConnected()) {
1040
			return false;
1041
		}
1042
 
1043
		$this->_setEmptyValues($query);
1044
		extract($query);
1045
 
1046
		if (!empty($order[0])) {
1047
			$order = array_shift($order);
1048
		}
1049
		$this->_stripAlias($conditions, $Model->alias);
1050
		$this->_stripAlias($fields, $Model->alias, false, 'value');
1051
		$this->_stripAlias($order, $Model->alias, false, 'both');
1052
 
1053
		if(!empty($conditions['id']) && empty($conditions['_id'])) {
1054
			$conditions['_id'] = $conditions['id'];
1055
			unset($conditions['id']);
1056
		}
1057
 
1058
		if (!empty($conditions['_id'])) {
1059
			$this->_convertId($conditions['_id']);
1060
		}
1061
 
1062
		$fields = (is_array($fields)) ? $fields : array($fields => 1);
1063
		if ($conditions === true) {
1064
			$conditions = array();
1065
		} elseif (!is_array($conditions)) {
1066
			$conditions = array($conditions);
1067
		}
1068
		$order = (is_array($order)) ? $order : array($order);
1069
 
1070
		if (is_array($order)) {
1071
			foreach($order as $field => &$dir) {
1072
				if (is_numeric($field) || is_null($dir)) {
1073
					unset ($order[$field]);
1074
					continue;
1075
				}
1076
				if ($dir && strtoupper($dir) === 'ASC') {
1077
					$dir = 1;
1078
					continue;
1079
				} elseif (!$dir || strtoupper($dir) === 'DESC') {
1080
					$dir = -1;
1081
					continue;
1082
				}
1083
				$dir = (int)$dir;
1084
			}
1085
		}
1086
 
1087
		if (empty($offset) && $page && $limit) {
1088
			$offset = ($page - 1) * $limit;
1089
		}
1090
 
1091
		$return = array();
1092
 
1093
		$this->_prepareLogQuery($Model); // just sets a timer
1094
        $table = $this->fullTableName($Model);
1095
		if (empty($modify)) {
1096
			if ($Model->findQueryType === 'count' && $fields == array('count' => true)) {
1097
				$cursor = $this->_db
1098
					->selectCollection($table)
1099
					->find($conditions, array('_id' => true));
1100
				if (!empty($hint)) {
1101
					$cursor->hint($hint);
1102
				}
1103
				$count = $cursor->count();
1104
				if ($this->fullDebug) {
1105
					if (empty($hint)) {
1106
						$hint = array();
1107
					}
1108
					$this->logQuery("db.{$table}.find( :conditions ).hint( :hint ).count()",
1109
						compact('conditions', 'count', 'hint')
1110
					);
1111
				}
1112
				return array(array($Model->alias => array('count' => $count)));
1113
			}
1114
 
1115
			$return = $this->_db
1116
				->selectCollection($table)
1117
				->find($conditions, $fields)
1118
				->sort($order)
1119
				->limit($limit)
1120
				->skip($offset);
1121
			if (!empty($hint)) {
1122
				$return->hint($hint);
1123
			}
1124
			if ($this->fullDebug) {
1125
				$count = $return->count(true);
1126
				if (empty($hint)) {
1127
					$hint = array();
1128
				}
1129
				$this->logQuery("db.{$table}.find( :conditions, :fields ).sort( :order ).limit( :limit ).skip( :offset ).hint( :hint )",
1130
					compact('conditions', 'fields', 'order', 'limit', 'offset', 'count', 'hint')
1131
				);
1132
			}
1133
		} else {
1134
			$options = array_filter(array(
1135
				'findandmodify' => $table,
1136
				'query' => $conditions,
1137
				'sort' => $order,
1138
				'remove' => !empty($remove),
1139
				'update' => $this->setMongoUpdateOperator($Model, $modify),
1140
				'new' => !empty($new),
1141
				'fields' => $fields,
1142
				'upsert' => !empty($upsert)
1143
			));
1144
			$return = $this->_db
1145
				->command($options);
1146
			if ($this->fullDebug) {
1147
				if ($return['ok']) {
1148
					$count = 1;
1149
					if ($this->config['set_string_id'] && !empty($return['value']['_id']) && is_object($return['value']['_id'])) {
1150
						$return['value']['_id'] = $return['value']['_id']->__toString();
1151
					}
1152
					$return[][$Model->alias] = $return['value'];
1153
				} else {
1154
					$count = 0;
1155
				}
1156
				$this->logQuery("db.runCommand( :options )",
1157
					array('options' => array_filter($options), 'count' => $count)
1158
				);
1159
			}
1160
		}
1161
 
1162
		if ($Model->findQueryType === 'count') {
1163
			return array(array($Model->alias => array('count' => $return->count())));
1164
		}
1165
 
1166
		if (is_object($return)) {
1167
			$_return = array();
1168
			while ($return->hasNext()) {
1169
				$mongodata = $return->getNext();
1170
				if ($this->config['set_string_id'] && !empty($mongodata['_id']) && is_object($mongodata['_id'])) {
1171
					$mongodata['_id'] = $mongodata['_id']->__toString();
1172
				}
1173
 
1174
				if ($Model->primaryKey !== '_id') {
1175
					$mongodata[$Model->primaryKey] = $mongodata['_id'];
1176
					unset($mongodata['_id']);
1177
				}
1178
				$_return[][$Model->alias] = $mongodata;
1179
			}
1180
			return $_return;
1181
		}
1182
		return $return;
1183
	}
1184
 
1185
/**
1186
 * rollback method
1187
 *
1188
 * MongoDB doesn't support transactions
1189
 *
1190
 * @return void
1191
 * @access public
1192
 */
1193
	public function rollback() {
1194
		return false;
1195
	}
1196
 
1197
/**
1198
 * Deletes all the records in a table
1199
 *
1200
 * @param mixed $table A string or model class representing the table to be truncated
1201
 * @return boolean
1202
 * @access public
1203
 */
1204
	public function truncate($table) {
1205
		if (!$this->isConnected()) {
1206
			return false;
1207
		}
1208
 
1209
		$fullTableName = $this->fullTableName($table);
1210
		$return = false;
1211
		try{
1212
			$return = $this->getMongoDb()->selectCollection($fullTableName)->remove(array());
1213
			if ($this->fullDebug) {
1214
				$this->logQuery("db.{$fullTableName}.remove({})");
1215
			}
1216
			$return = true;
1217
		} catch (MongoException $e) {
1218
			$this->error = $e->getMessage();
1219
			trigger_error($this->error);
1220
		}
1221
		return $return;
1222
	}
1223
 
1224
/**
1225
 * query method
1226
 *  If call getMongoDb() from model, this method call getMongoDb().
1227
 *
1228
 * @param mixed $query
1229
 * @param array $params array()
1230
 * @return void
1231
 * @access public
1232
 */
1233
	public function query() {
1234
		$args = func_get_args();
1235
		$query = $args[0];
1236
		$params = array();
1237
		if(count($args) > 1) {
1238
			$params = $args[1];
1239
		}
1240
 
1241
		if (!$this->isConnected()) {
1242
			return false;
1243
		}
1244
 
1245
		if($query === 'getMongoDb') {
1246
			return $this->getMongoDb();
1247
		}
1248
 
1249
		if (count($args) > 1 && (strpos($args[0], 'findBy') === 0 || strpos($args[0], 'findAllBy') === 0)) {
1250
			$params = $args[1];
1251
 
1252
			if (substr($args[0], 0, 6) === 'findBy') {
1253
				$field = Inflector::underscore(substr($args[0], 6));
1254
				return $args[2]->find('first', array('conditions' => array($field => $args[1][0])));
1255
			} else{
1256
				$field = Inflector::underscore(substr($args[0], 9));
1257
				return $args[2]->find('all', array('conditions' => array($field => $args[1][0])));
1258
			}
1259
		}
1260
 
1261
		if(isset($args[2]) && is_a($args[2], 'Model')) {
1262
			$this->_prepareLogQuery($args[2]);
1263
		}
1264
 
1265
		$return = $this->_db
1266
			->command($query);
1267
		if ($this->fullDebug) {
1268
			$this->logQuery("db.runCommand( :query )", 	compact('query'));
1269
		}
1270
 
1271
		return $return;
1272
	}
1273
 
1274
/**
1275
 * mapReduce
1276
 *
1277
 * @param mixed $query
1278
 * @param integer $timeout (milli second)
1279
 * @return mixed false or array
1280
 * @access public
1281
 */
1282
	public function mapReduce($query, $timeout = null) {
1283
 
1284
		//above MongoDB1.8, query must object.
1285
		if(isset($query['query']) && !is_object($query['query'])) {
1286
			$query['query'] = (object)$query['query'];
1287
		}
1288
 
1289
		$result = $this->query($query);
1290
 
1291
		if($result['ok']) {
1292
			if (isset($query['out']['inline']) && $query['out']['inline'] === 1) {
1293
				if (is_array($result['results'])) {
1294
					$data = $result['results'];
1295
				}else{
1296
					$data = false;
1297
				}
1298
			}else {
1299
				$data = $this->_db->selectCollection($result['result'])->find();
1300
				if(!empty($timeout)) {
1301
					$data->timeout($timeout);
1302
				}
1303
			}
1304
			return $data;
1305
		}
1306
		return false;
1307
	}
1308
 
1309
 
1310
 
1311
/**
1312
 * Prepares a value, or an array of values for database queries by quoting and escaping them.
1313
 *
1314
 * @param mixed $data A value or an array of values to prepare.
1315
 * @param string $column The column into which this data will be inserted
1316
 * @return mixed Prepared value or array of values.
1317
 * @access public
1318
 */
1319
	public function value($data, $column = null) {
1320
		if (is_array($data) && !empty($data)) {
1321
			return array_map(
1322
				array(&$this, 'value'),
1323
				$data, array_fill(0, count($data), $column)
1324
			);
1325
		} elseif (is_object($data) && isset($data->type, $data->value)) {
1326
			if ($data->type == 'identifier') {
1327
				return $this->name($data->value);
1328
			} elseif ($data->type == 'expression') {
1329
				return $data->value;
1330
			}
1331
		} elseif (in_array($data, array('{$__cakeID__$}', '{$__cakeForeignKey__$}'), true)) {
1332
			return $data;
1333
		}
1334
 
1335
		if ($data === null || (is_array($data) && empty($data))) {
1336
			return 'NULL';
1337
		}
1338
 
1339
		if (empty($column)) {
1340
			$column = $this->introspectType($data);
1341
		}
1342
 
1343
		switch ($column) {
1344
			case 'binary':
1345
			case 'string':
1346
			case 'text':
1347
				return $data;
1348
			case 'boolean':
1349
				return !empty($data);
1350
			default:
1351
				if ($data === '') {
1352
					return 'NULL';
1353
				}
1354
				if (is_float($data)) {
1355
					return str_replace(',', '.', strval($data));
1356
				}
1357
 
1358
				return $data;
1359
		}
1360
	}
1361
 
1362
/**
1363
 * execute method
1364
 *
1365
 * If there is no query or the query is true, execute has probably been called as part of a
1366
 * db-agnostic process which does not have a mongo equivalent, don't do anything.
1367
 *
1368
 * @param mixed $query
1369
 * @param array $options
1370
 * @param array $params array()
1371
 * @return void
1372
 * @access public
1373
 */
1374
	public function execute($query, $options = array(), $params = array()) {
1375
		if (!$this->isConnected()) {
1376
			return false;
1377
		}
1378
 
1379
		if (!$query || $query === true) {
1380
			return;
1381
		}
1382
		$this->_prepareLogQuery($Model); // just sets a timer
1383
		$return = $this->_db
1384
			->execute($query, $params);
1385
		if ($this->fullDebug) {
1386
			if ($params) {
1387
				$this->logQuery(":query, :params",
1388
					compact('query', 'params')
1389
				);
1390
			} else {
1391
				$this->logQuery($query);
1392
			}
1393
		}
1394
		if ($return['ok']) {
1395
			return $return['retval'];
1396
		}
1397
		return $return;
1398
	}
1399
 
1400
/**
1401
 * Set empty values, arrays or integers, for the variables Mongo uses
1402
 *
1403
 * @param mixed $data
1404
 * @param array $integers array('limit', 'offset')
1405
 * @return void
1406
 * @access protected
1407
 */
1408
	protected function _setEmptyValues(&$data, $integers = array('limit', 'offset')) {
1409
		if (!is_array($data)) {
1410
			return;
1411
		}
1412
		foreach($data as $key => $value) {
1413
			if (empty($value)) {
1414
				if (in_array($key, $integers)) {
1415
					$data[$key] = 0;
1416
				} else {
1417
					$data[$key] = array();
1418
				}
1419
			}
1420
		}
1421
	}
1422
 
1423
/**
1424
 * prepareLogQuery method
1425
 *
1426
 * Any prep work to log a query
1427
 *
1428
 * @param mixed $Model
1429
 * @return void
1430
 * @access protected
1431
 */
1432
	protected function _prepareLogQuery(&$Model) {
1433
		if (!$this->fullDebug) {
1434
			return false;
1435
		}
1436
		$this->_startTime = microtime(true);
1437
		$this->took = null;
1438
		$this->affected = null;
1439
		$this->error = null;
1440
		$this->numRows = null;
1441
		return true;
1442
	}
1443
 
1444
/**
1445
 * setTimeout Method
1446
 *
1447
 * Sets the MongoCursor timeout so long queries (like map / reduce) can run at will.
1448
 * Expressed in milliseconds, for an infinite timeout, set to -1
1449
 *
1450
 * @param int $ms
1451
 * @return boolean
1452
 * @access public
1453
 */
1454
	public function setTimeout($ms){
1455
		MongoCursor::$timeout = $ms;
1456
 
1457
		return true;
1458
	}
1459
 
1460
/**
1461
 * logQuery method
1462
 *
1463
 * Set timers, errors and refer to the parent
1464
 * If there are arguments passed - inject them into the query
1465
 * Show MongoIds in a copy-and-paste-into-mongo format
1466
 *
1467
 *
1468
 * @param mixed $query
1469
 * @param array $args array()
1470
 * @return void
1471
 * @access public
1472
 */
1473
	public function logQuery($query, $args = array()) {
1474
		if ($args) {
1475
			$this->_stringify($args);
1476
			$query = String::insert($query, $args);
1477
		}
1478
		$this->took = round((microtime(true) - $this->_startTime) * 1000, 0);
1479
		$this->affected = null;
1480
		if (empty($this->error['err'])) {
1481
			$this->error = $this->_db->lastError();
1482
			if (!is_scalar($this->error)) {
1483
				$this->error = json_encode($this->error);
1484
			}
1485
		}
1486
		$this->numRows = !empty($args['count'])?$args['count']:null;
1487
 
1488
		$query = preg_replace('@"ObjectId\((.*?)\)"@', 'ObjectId ("\1")', $query);
1489
		return parent::logQuery($query);
1490
	}
1491
 
1492
/**
1493
 * convertId method
1494
 *
1495
 * $conditions is used to determine if it should try to auto correct _id => array() queries
1496
 * it only appies to conditions, hence the param name
1497
 *
1498
 * @param mixed $mixed
1499
 * @param bool $conditions false
1500
 * @return void
1501
 * @access protected
1502
 */
1503
	protected function _convertId(&$mixed, $conditions = false) {
1504
		if (is_int($mixed) || ctype_digit($mixed)) {
1505
			return;
1506
		}
1507
		if (is_string($mixed)) {
1508
			if (strlen($mixed) !== 24) {
1509
				return;
1510
			}
1511
			$mixed = new MongoId($mixed);
1512
		}
1513
		if (is_array($mixed)) {
1514
			foreach($mixed as &$row) {
1515
				$this->_convertId($row, false);
1516
			}
1517
			if (!empty($mixed[0]) && $conditions) {
1518
				$mixed = array('$in' => $mixed);
1519
			}
1520
		}
1521
	}
1522
 
1523
/**
1524
 * stringify method
1525
 *
1526
 * Takes an array of args as an input and returns an array of json-encoded strings. Takes care of
1527
 * any objects the arrays might be holding (MongoID);
1528
 *
1529
 * @param array $args array()
1530
 * @param int $level 0 internal recursion counter
1531
 * @return array
1532
 * @access protected
1533
 */
1534
	protected function _stringify(&$args = array(), $level = 0) {
1535
		foreach($args as &$arg) {
1536
			if (is_array($arg)) {
1537
				$this->_stringify($arg, $level + 1);
1538
			} elseif (is_object($arg) && is_callable(array($arg, '__toString'))) {
1539
				$class = get_class($arg);
1540
				if ($class === 'MongoId') {
1541
					$arg = 'ObjectId(' . $arg->__toString() . ')';
1542
				} elseif ($class === 'MongoRegex') {
1543
					$arg = '_regexstart_' . $arg->__toString() . '_regexend_';
1544
				} else {
1545
					$arg = $class . '(' . $arg->__toString() . ')';
1546
				}
1547
			}
1548
			if ($level === 0) {
1549
				$arg = json_encode($arg);
1550
				if (strpos($arg, '_regexstart_')) {
1551
					preg_match_all('@"_regexstart_(.*?)_regexend_"@', $arg, $matches);
1552
					foreach($matches[0] as $i => $whole) {
1553
						$replace = stripslashes($matches[1][$i]);
1554
						$arg = str_replace($whole, $replace, $arg);
1555
					}
1556
				}
1557
			}
1558
		}
1559
	}
1560
 
1561
/**
1562
 * Convert automatically array('Model.field' => 'foo') to array('field' => 'foo')
1563
 *
1564
 * This introduces the limitation that you can't have a (nested) field with the same name as the model
1565
 * But it's a small price to pay to be able to use other behaviors/functionality with mongoDB
1566
 *
1567
 * @param array $args array()
1568
 * @param string $alias 'Model'
1569
 * @param bool $recurse true
1570
 * @param string $check 'key', 'value' or 'both'
1571
 * @return void
1572
 * @access protected
1573
 */
1574
	protected function _stripAlias(&$args = array(), $alias = 'Model', $recurse = true, $check = 'key') {
1575
		if (!is_array($args)) {
1576
			return;
1577
		}
1578
		$checkKey = ($check === 'key' || $check === 'both');
1579
		$checkValue = ($check === 'value' || $check === 'both');
1580
 
1581
		foreach($args as $key => &$val) {
1582
			if ($checkKey) {
1583
				if (strpos($key, $alias . '.') === 0) {
1584
					unset($args[$key]);
1585
					$key = substr($key, strlen($alias) + 1);
1586
					$args[$key] = $val;
1587
				}
1588
			}
1589
			if ($checkValue) {
1590
				if (is_string($val) && strpos($val, $alias . '.') === 0) {
1591
					$val = substr($val, strlen($alias) + 1);
1592
				}
1593
			}
1594
			if ($recurse && is_array($val)) {
1595
				$this->_stripAlias($val, $alias, true, $check);
1596
			}
1597
		}
1598
	}
1599
}
1600
 
1601
/**
1602
 * MongoDbDateFormatter method
1603
 *
1604
 * This function cannot be in the class because of the way model save is written
1605
 *
1606
 * @param mixed $date null
1607
 * @return void
1608
 * @access public
1609
 */
1610
function MongoDbDateFormatter($date = null) {
1611
	if ($date) {
1612
		return new MongoDate($date);
1613
	}
1614
	return new MongoDate();
1615
}