Subversion Repositories SmartDukaan

Rev

Rev 14098 | Details | Compare with Previous | 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
 */
15767 anikendra 671
	//public function group($params, Model $Model = null) {
672
	public function group($params, $Model = null) {
14098 anikendra 673
 
674
		if (!$this->isConnected() || count($params) === 0 || $Model === null) {
675
			return false;
676
		}
677
 
678
		$this->_prepareLogQuery($Model); // just sets a timer
679
 
680
		$key = (empty($params['key'])) ? array() : $params['key'];
681
		$initial = (empty($params['initial'])) ? array() : $params['initial'];
682
		$reduce = (empty($params['reduce'])) ? array() : $params['reduce'];
683
		$options = (empty($params['options'])) ? array() : $params['options'];
684
        $table = $this->fullTableName($Model);
685
 
686
		try{
687
			$return = $this->_db
688
				->selectCollection($table)
689
				->group($key, $initial, $reduce, $options);
690
		} catch (MongoException $e) {
691
			$this->error = $e->getMessage();
692
			trigger_error($this->error);
693
		}
694
		if ($this->fullDebug) {
695
			$this->logQuery("db.{$table}.group( :key, :initial, :reduce, :options )", $params);
696
		}
697
 
698
		return $return;
699
	}
700
 
701
 
702
/**
703
 * ensureIndex method
704
 *
705
 * @param mixed $Model
706
 * @param array $keys array()
707
 * @param array $params array()
708
 * @return void
709
 * @access public
710
 */
711
	public function ensureIndex(&$Model, $keys = array(), $params = array()) {
712
		if (!$this->isConnected()) {
713
			return false;
714
		}
715
 
716
		$this->_prepareLogQuery($Model); // just sets a timer
717
        $table = $this->fullTableName($Model);
718
 
719
		try{
720
			$return = $this->_db
721
				->selectCollection($table)
722
				->ensureIndex($keys, $params);
723
		} catch (MongoException $e) {
724
			$this->error = $e->getMessage();
725
			trigger_error($this->error);
726
		}
727
		if ($this->fullDebug) {
728
			$this->logQuery("db.{$table}.ensureIndex( :keys, :params )", compact('keys', 'params'));
729
		}
730
 
731
		return $return;
732
	}
733
 
734
/**
735
 * Update Data
736
 *
737
 * This method uses $set operator automatically with MongoCollection::update().
738
 * If you don't want to use $set operator, you can chose any one as follw.
739
 *  1. Set TRUE in Model::mongoNoSetOperator property.
740
 *  2. Set a mongodb operator in a key of save data as follow.
741
 *      Model->save(array('_id' => $id, '$inc' => array('count' => 1)));
742
 *      Don't use Model::mongoSchema property,
743
 *       CakePHP delete '$inc' data in Model::Save().
744
 *  3. Set a Mongo operator in Model::mongoNoSetOperator property.
745
 *      Model->mongoNoSetOperator = '$inc';
746
 *      Model->save(array('_id' => $id, array('count' => 1)));
747
 *
748
 * @param Model $Model Model Instance
749
 * @param array $fields Field data
750
 * @param array $values Save data
751
 * @return boolean Update result
752
 * @access public
753
 */
754
	public function update(Model $Model, $fields = null, $values = null, $conditions = null) {
755
		if (!$this->isConnected()) {
756
			return false;
757
		}
758
 
759
		if ($fields !== null && $values !== null) {
760
			$data = array_combine($fields, $values);
761
		} elseif($fields !== null && $conditions !== null) {
762
			return $this->updateAll($Model, $fields, $conditions);
763
		} else{
764
			$data = $Model->data;
765
		}
766
 
767
		if($Model->primaryKey !== '_id' && isset($data[$Model->primaryKey]) && !empty($data[$Model->primaryKey])) {
768
			$data['_id'] = $data[$Model->primaryKey];
769
			unset($data[$Model->primaryKey]);
770
		}
771
 
772
		if (empty($data['_id'])) {
773
			$data['_id'] = $Model->id;
774
		}
775
 
776
		$this->_convertId($data['_id']);
777
        $table = $this->fullTableName($Model);
778
 
779
		try{
780
			$mongoCollectionObj = $this->_db
781
				->selectCollection($table);
782
		} catch (MongoException $e) {
783
			$this->error = $e->getMessage();
784
			trigger_error($this->error);
785
			return false;
786
		}
787
 
788
		$this->_prepareLogQuery($Model); // just sets a timer
789
		if (!empty($data['_id'])) {
790
			$this->_convertId($data['_id']);
791
			$cond = array('_id' => $data['_id']);
792
			unset($data['_id']);
793
 
794
			$data = $this->setMongoUpdateOperator($Model, $data);
795
 
796
			try{
797
				if ($this->_driverVersion >= '1.3.0') {
798
					$return = $mongoCollectionObj->update($cond, $data, array("multiple" => false, 'safe' => true));
799
				} else {
800
					$return = $mongoCollectionObj->update($cond, $data, array("multiple" => false));
801
				}
802
			} catch (MongoException $e) {
803
				$this->error = $e->getMessage();
804
				trigger_error($this->error);
805
			}
806
			if ($this->fullDebug) {
807
				$this->logQuery("db.{$table}.update( :conditions, :data, :params )",
808
					array('conditions' => $cond, 'data' => $data, 'params' => array("multiple" => false))
809
				);
810
			}
811
		} else {
812
			try{
813
				if ($this->_driverVersion >= '1.3.0') {
814
					$return = $mongoCollectionObj->save($data, array('safe' => true));
815
				} else {
816
					$return = $mongoCollectionObj->save($data);
817
				}
818
			} catch (MongoException $e) {
819
				$this->error = $e->getMessage();
820
				trigger_error($this->error);
821
			}
822
			if ($this->fullDebug) {
823
				$this->logQuery("db.{$table}.save( :data )", compact('data'));
824
			}
825
		}
826
		return $return;
827
	}
828
 
829
 
830
/**
831
 * setMongoUpdateOperator
832
 *
833
 * Set Mongo update operator following saving data.
834
 * This method is for update() and updateAll.
835
 *
836
 * @param Model $Model Model Instance
837
 * @param array $values Save data
838
 * @return array $data
839
 * @access public
840
 */
841
	public function setMongoUpdateOperator(&$Model, $data) {
842
		if(isset($data['updated'])) {
843
			$updateField = 'updated';
844
		} else {
845
			$updateField = 'modified';
846
		}
847
 
848
		//setting Mongo operator
849
		if(empty($Model->mongoNoSetOperator)) {
850
			if(!preg_grep('/^\$/', array_keys($data))) {
851
				$data = array('$set' => $data);
852
			} else {
853
				if(!empty($data[$updateField])) {
854
					$modified = $data[$updateField];
855
					unset($data[$updateField]);
856
					$data['$set'] = array($updateField => $modified);
857
				}
858
			}
859
		} elseif(substr($Model->mongoNoSetOperator,0,1) === '$') {
860
			if(!empty($data[$updateField])) {
861
				$modified = $data[$updateField];
862
				unset($data[$updateField]);
863
				$data = array($Model->mongoNoSetOperator => $data, '$set' => array($updateField => $modified));
864
			} else {
865
				$data = array($Model->mongoNoSetOperator => $data);
866
 
867
			}
868
		}
869
 
870
		return $data;
871
	}
872
 
873
/**
874
 * Update multiple Record
875
 *
876
 * @param Model $Model Model Instance
877
 * @param array $fields Field data
878
 * @param array $conditions
879
 * @return boolean Update result
880
 * @access public
881
 */
882
	public function updateAll(&$Model, $fields = null,  $conditions = null) {
883
		if (!$this->isConnected()) {
884
			return false;
885
		}
886
 
887
		$this->_stripAlias($conditions, $Model->alias);
888
		$this->_stripAlias($fields, $Model->alias, false, 'value');
889
 
890
		$fields = $this->setMongoUpdateOperator($Model, $fields);
891
 
892
		$this->_prepareLogQuery($Model); // just sets a timer
893
        $table = $this->fullTableName($Model);
894
		try{
895
			if ($this->_driverVersion >= '1.3.0') {
896
				// not use 'upsert'
897
				$return = $this->_db
898
					->selectCollection($table)
899
					->update($conditions, $fields, array("multiple" => true, 'safe' => true));
900
				if (isset($return['updatedExisting'])) {
901
					$return = $return['updatedExisting'];
902
				}
903
			} else {
904
				$return = $this->_db
905
					->selectCollection($table)
906
					->update($conditions, $fields, array("multiple" => true));
907
			}
908
		} catch (MongoException $e) {
909
			$this->error = $e->getMessage();
910
			trigger_error($this->error);
911
		}
912
 
913
		if ($this->fullDebug) {
914
			$this->logQuery("db.{$table}.update( :conditions, :fields, :params )",
915
				array('conditions' => $conditions, 'fields' => $fields, 'params' => array("multiple" => true))
916
			);
917
		}
918
		return $return;
919
	}
920
 
921
/**
922
 * deriveSchemaFromData method
923
 *
924
 * @param mixed $Model
925
 * @param array $data array()
926
 * @return void
927
 * @access public
928
 */
929
	public function deriveSchemaFromData($Model, $data = array()) {
930
		if (!$data) {
931
			$data = $Model->data;
932
			if ($data && array_key_exists($Model->alias, $data)) {
933
				$data = $data[$Model->alias];
934
			}
935
		}
936
 
937
		$return = $this->_defaultSchema;
938
 
939
		if ($data) {
940
			$fields = array_keys($data);
941
			foreach($fields as $field) {
942
				if (in_array($field, array('created', 'modified', 'updated'))) {
943
					$return[$field] = array('type' => 'datetime', 'null' => true);
944
				} else {
945
					$return[$field] = array('type' => 'string', 'length' => 2000);
946
				}
947
			}
948
		}
949
 
950
		return $return;
951
	}
952
 
953
/**
954
 * Delete Data
955
 *
956
 * For deleteAll(true, false) calls - conditions will arrive here as true - account for that and
957
 * convert to an empty array
958
 * For deleteAll(array('some conditions')) calls - conditions will arrive here as:
959
 *  array(
960
 *  	Alias._id => array(1, 2, 3, ...)
961
 *  )
962
 *
963
 * This format won't be understood by mongodb, it'll find 0 rows. convert to:
964
 *
965
 *  array(
966
 *  	Alias._id => array('$in' => array(1, 2, 3, ...))
967
 *  )
968
 *
969
 * @TODO bench remove() v drop. if it's faster to drop - just drop the collection taking into
970
 *  	account existing indexes (recreate just the indexes)
971
 * @param Model $Model Model Instance
972
 * @param array $conditions
973
 * @return boolean Update result
974
 * @access public
975
 */
976
	public function delete(Model $Model, $conditions = null) {
977
		if (!$this->isConnected()) {
978
			return false;
979
		}
980
 
981
		$id = null;
982
 
983
		$this->_stripAlias($conditions, $Model->alias);
984
 
985
		if ($conditions === true) {
986
			$conditions = array();
987
		} elseif (empty($conditions)) {
988
			$id = $Model->id;
989
		} elseif (!empty($conditions) && !is_array($conditions)) {
990
			$id = $conditions;
991
			$conditions = array();
992
		} elseif (!empty($conditions['id'])) { //for cakephp2.0
993
			$id = $conditions['id'];
994
			unset($conditions['id']);
995
		}
996
 
997
        $table = $this->fullTableName($Model);
998
 
999
		$mongoCollectionObj = $this->_db
1000
			->selectCollection($table);
1001
 
1002
		$this->_stripAlias($conditions, $Model->alias);
1003
		if (!empty($id)) {
1004
			$conditions['_id'] = $id;
1005
		}
1006
		if (!empty($conditions['_id'])) {
1007
			$this->_convertId($conditions['_id'], true);
1008
		}
1009
 
1010
		$return = false;
1011
		$r = false;
1012
		try{
1013
			$this->_prepareLogQuery($Model); // just sets a timer
1014
			$return = $mongoCollectionObj->remove($conditions);
1015
			if ($this->fullDebug) {
1016
				$this->logQuery("db.{$table}.remove( :conditions )",
1017
					compact('conditions')
1018
				);
1019
			}
1020
			$return = true;
1021
		} catch (MongoException $e) {
1022
			$this->error = $e->getMessage();
1023
			trigger_error($this->error);
1024
		}
1025
		return $return;
1026
	}
1027
 
1028
/**
1029
 * Read Data
1030
 *
1031
 * For deleteAll(true) calls - the conditions will arrive here as true - account for that and switch to an empty array
1032
 *
1033
 * @param Model $Model Model Instance
1034
 * @param array $query Query data
1035
 * @param mixed  $recursive
1036
 * @return array Results
1037
 * @access public
1038
 */
1039
	public function read(Model $Model, $query = array(), $recursive = null) {
1040
		if (!$this->isConnected()) {
1041
			return false;
1042
		}
1043
 
1044
		$this->_setEmptyValues($query);
1045
		extract($query);
1046
 
1047
		if (!empty($order[0])) {
1048
			$order = array_shift($order);
1049
		}
1050
		$this->_stripAlias($conditions, $Model->alias);
1051
		$this->_stripAlias($fields, $Model->alias, false, 'value');
1052
		$this->_stripAlias($order, $Model->alias, false, 'both');
1053
 
1054
		if(!empty($conditions['id']) && empty($conditions['_id'])) {
1055
			$conditions['_id'] = $conditions['id'];
1056
			unset($conditions['id']);
1057
		}
1058
 
1059
		if (!empty($conditions['_id'])) {
1060
			$this->_convertId($conditions['_id']);
1061
		}
1062
 
1063
		$fields = (is_array($fields)) ? $fields : array($fields => 1);
1064
		if ($conditions === true) {
1065
			$conditions = array();
1066
		} elseif (!is_array($conditions)) {
1067
			$conditions = array($conditions);
1068
		}
1069
		$order = (is_array($order)) ? $order : array($order);
1070
 
1071
		if (is_array($order)) {
1072
			foreach($order as $field => &$dir) {
1073
				if (is_numeric($field) || is_null($dir)) {
1074
					unset ($order[$field]);
1075
					continue;
1076
				}
1077
				if ($dir && strtoupper($dir) === 'ASC') {
1078
					$dir = 1;
1079
					continue;
1080
				} elseif (!$dir || strtoupper($dir) === 'DESC') {
1081
					$dir = -1;
1082
					continue;
1083
				}
1084
				$dir = (int)$dir;
1085
			}
1086
		}
1087
 
1088
		if (empty($offset) && $page && $limit) {
1089
			$offset = ($page - 1) * $limit;
1090
		}
1091
 
1092
		$return = array();
1093
 
1094
		$this->_prepareLogQuery($Model); // just sets a timer
1095
        $table = $this->fullTableName($Model);
1096
		if (empty($modify)) {
1097
			if ($Model->findQueryType === 'count' && $fields == array('count' => true)) {
1098
				$cursor = $this->_db
1099
					->selectCollection($table)
1100
					->find($conditions, array('_id' => true));
1101
				if (!empty($hint)) {
1102
					$cursor->hint($hint);
1103
				}
1104
				$count = $cursor->count();
1105
				if ($this->fullDebug) {
1106
					if (empty($hint)) {
1107
						$hint = array();
1108
					}
1109
					$this->logQuery("db.{$table}.find( :conditions ).hint( :hint ).count()",
1110
						compact('conditions', 'count', 'hint')
1111
					);
1112
				}
1113
				return array(array($Model->alias => array('count' => $count)));
1114
			}
1115
 
1116
			$return = $this->_db
1117
				->selectCollection($table)
1118
				->find($conditions, $fields)
1119
				->sort($order)
1120
				->limit($limit)
1121
				->skip($offset);
1122
			if (!empty($hint)) {
1123
				$return->hint($hint);
1124
			}
1125
			if ($this->fullDebug) {
1126
				$count = $return->count(true);
1127
				if (empty($hint)) {
1128
					$hint = array();
1129
				}
1130
				$this->logQuery("db.{$table}.find( :conditions, :fields ).sort( :order ).limit( :limit ).skip( :offset ).hint( :hint )",
1131
					compact('conditions', 'fields', 'order', 'limit', 'offset', 'count', 'hint')
1132
				);
1133
			}
1134
		} else {
1135
			$options = array_filter(array(
1136
				'findandmodify' => $table,
1137
				'query' => $conditions,
1138
				'sort' => $order,
1139
				'remove' => !empty($remove),
1140
				'update' => $this->setMongoUpdateOperator($Model, $modify),
1141
				'new' => !empty($new),
1142
				'fields' => $fields,
1143
				'upsert' => !empty($upsert)
1144
			));
1145
			$return = $this->_db
1146
				->command($options);
1147
			if ($this->fullDebug) {
1148
				if ($return['ok']) {
1149
					$count = 1;
1150
					if ($this->config['set_string_id'] && !empty($return['value']['_id']) && is_object($return['value']['_id'])) {
1151
						$return['value']['_id'] = $return['value']['_id']->__toString();
1152
					}
1153
					$return[][$Model->alias] = $return['value'];
1154
				} else {
1155
					$count = 0;
1156
				}
1157
				$this->logQuery("db.runCommand( :options )",
1158
					array('options' => array_filter($options), 'count' => $count)
1159
				);
1160
			}
1161
		}
1162
 
1163
		if ($Model->findQueryType === 'count') {
1164
			return array(array($Model->alias => array('count' => $return->count())));
1165
		}
1166
 
1167
		if (is_object($return)) {
1168
			$_return = array();
1169
			while ($return->hasNext()) {
1170
				$mongodata = $return->getNext();
1171
				if ($this->config['set_string_id'] && !empty($mongodata['_id']) && is_object($mongodata['_id'])) {
1172
					$mongodata['_id'] = $mongodata['_id']->__toString();
1173
				}
1174
 
1175
				if ($Model->primaryKey !== '_id') {
1176
					$mongodata[$Model->primaryKey] = $mongodata['_id'];
1177
					unset($mongodata['_id']);
1178
				}
1179
				$_return[][$Model->alias] = $mongodata;
1180
			}
1181
			return $_return;
1182
		}
1183
		return $return;
1184
	}
1185
 
1186
/**
1187
 * rollback method
1188
 *
1189
 * MongoDB doesn't support transactions
1190
 *
1191
 * @return void
1192
 * @access public
1193
 */
1194
	public function rollback() {
1195
		return false;
1196
	}
1197
 
1198
/**
1199
 * Deletes all the records in a table
1200
 *
1201
 * @param mixed $table A string or model class representing the table to be truncated
1202
 * @return boolean
1203
 * @access public
1204
 */
1205
	public function truncate($table) {
1206
		if (!$this->isConnected()) {
1207
			return false;
1208
		}
1209
 
1210
		$fullTableName = $this->fullTableName($table);
1211
		$return = false;
1212
		try{
1213
			$return = $this->getMongoDb()->selectCollection($fullTableName)->remove(array());
1214
			if ($this->fullDebug) {
1215
				$this->logQuery("db.{$fullTableName}.remove({})");
1216
			}
1217
			$return = true;
1218
		} catch (MongoException $e) {
1219
			$this->error = $e->getMessage();
1220
			trigger_error($this->error);
1221
		}
1222
		return $return;
1223
	}
1224
 
1225
/**
1226
 * query method
1227
 *  If call getMongoDb() from model, this method call getMongoDb().
1228
 *
1229
 * @param mixed $query
1230
 * @param array $params array()
1231
 * @return void
1232
 * @access public
1233
 */
1234
	public function query() {
1235
		$args = func_get_args();
1236
		$query = $args[0];
1237
		$params = array();
1238
		if(count($args) > 1) {
1239
			$params = $args[1];
1240
		}
1241
 
1242
		if (!$this->isConnected()) {
1243
			return false;
1244
		}
1245
 
1246
		if($query === 'getMongoDb') {
1247
			return $this->getMongoDb();
1248
		}
1249
 
1250
		if (count($args) > 1 && (strpos($args[0], 'findBy') === 0 || strpos($args[0], 'findAllBy') === 0)) {
1251
			$params = $args[1];
1252
 
1253
			if (substr($args[0], 0, 6) === 'findBy') {
1254
				$field = Inflector::underscore(substr($args[0], 6));
1255
				return $args[2]->find('first', array('conditions' => array($field => $args[1][0])));
1256
			} else{
1257
				$field = Inflector::underscore(substr($args[0], 9));
1258
				return $args[2]->find('all', array('conditions' => array($field => $args[1][0])));
1259
			}
1260
		}
1261
 
1262
		if(isset($args[2]) && is_a($args[2], 'Model')) {
1263
			$this->_prepareLogQuery($args[2]);
1264
		}
1265
 
1266
		$return = $this->_db
1267
			->command($query);
1268
		if ($this->fullDebug) {
1269
			$this->logQuery("db.runCommand( :query )", 	compact('query'));
1270
		}
1271
 
1272
		return $return;
1273
	}
1274
 
1275
/**
1276
 * mapReduce
1277
 *
1278
 * @param mixed $query
1279
 * @param integer $timeout (milli second)
1280
 * @return mixed false or array
1281
 * @access public
1282
 */
1283
	public function mapReduce($query, $timeout = null) {
1284
 
1285
		//above MongoDB1.8, query must object.
1286
		if(isset($query['query']) && !is_object($query['query'])) {
1287
			$query['query'] = (object)$query['query'];
1288
		}
1289
 
1290
		$result = $this->query($query);
1291
 
1292
		if($result['ok']) {
1293
			if (isset($query['out']['inline']) && $query['out']['inline'] === 1) {
1294
				if (is_array($result['results'])) {
1295
					$data = $result['results'];
1296
				}else{
1297
					$data = false;
1298
				}
1299
			}else {
1300
				$data = $this->_db->selectCollection($result['result'])->find();
1301
				if(!empty($timeout)) {
1302
					$data->timeout($timeout);
1303
				}
1304
			}
1305
			return $data;
1306
		}
1307
		return false;
1308
	}
1309
 
1310
 
1311
 
1312
/**
1313
 * Prepares a value, or an array of values for database queries by quoting and escaping them.
1314
 *
1315
 * @param mixed $data A value or an array of values to prepare.
1316
 * @param string $column The column into which this data will be inserted
1317
 * @return mixed Prepared value or array of values.
1318
 * @access public
1319
 */
1320
	public function value($data, $column = null) {
1321
		if (is_array($data) && !empty($data)) {
1322
			return array_map(
1323
				array(&$this, 'value'),
1324
				$data, array_fill(0, count($data), $column)
1325
			);
1326
		} elseif (is_object($data) && isset($data->type, $data->value)) {
1327
			if ($data->type == 'identifier') {
1328
				return $this->name($data->value);
1329
			} elseif ($data->type == 'expression') {
1330
				return $data->value;
1331
			}
1332
		} elseif (in_array($data, array('{$__cakeID__$}', '{$__cakeForeignKey__$}'), true)) {
1333
			return $data;
1334
		}
1335
 
1336
		if ($data === null || (is_array($data) && empty($data))) {
1337
			return 'NULL';
1338
		}
1339
 
1340
		if (empty($column)) {
1341
			$column = $this->introspectType($data);
1342
		}
1343
 
1344
		switch ($column) {
1345
			case 'binary':
1346
			case 'string':
1347
			case 'text':
1348
				return $data;
1349
			case 'boolean':
1350
				return !empty($data);
1351
			default:
1352
				if ($data === '') {
1353
					return 'NULL';
1354
				}
1355
				if (is_float($data)) {
1356
					return str_replace(',', '.', strval($data));
1357
				}
1358
 
1359
				return $data;
1360
		}
1361
	}
1362
 
1363
/**
1364
 * execute method
1365
 *
1366
 * If there is no query or the query is true, execute has probably been called as part of a
1367
 * db-agnostic process which does not have a mongo equivalent, don't do anything.
1368
 *
1369
 * @param mixed $query
1370
 * @param array $options
1371
 * @param array $params array()
1372
 * @return void
1373
 * @access public
1374
 */
1375
	public function execute($query, $options = array(), $params = array()) {
1376
		if (!$this->isConnected()) {
1377
			return false;
1378
		}
1379
 
1380
		if (!$query || $query === true) {
1381
			return;
1382
		}
1383
		$this->_prepareLogQuery($Model); // just sets a timer
1384
		$return = $this->_db
1385
			->execute($query, $params);
1386
		if ($this->fullDebug) {
1387
			if ($params) {
1388
				$this->logQuery(":query, :params",
1389
					compact('query', 'params')
1390
				);
1391
			} else {
1392
				$this->logQuery($query);
1393
			}
1394
		}
1395
		if ($return['ok']) {
1396
			return $return['retval'];
1397
		}
1398
		return $return;
1399
	}
1400
 
1401
/**
1402
 * Set empty values, arrays or integers, for the variables Mongo uses
1403
 *
1404
 * @param mixed $data
1405
 * @param array $integers array('limit', 'offset')
1406
 * @return void
1407
 * @access protected
1408
 */
1409
	protected function _setEmptyValues(&$data, $integers = array('limit', 'offset')) {
1410
		if (!is_array($data)) {
1411
			return;
1412
		}
1413
		foreach($data as $key => $value) {
1414
			if (empty($value)) {
1415
				if (in_array($key, $integers)) {
1416
					$data[$key] = 0;
1417
				} else {
1418
					$data[$key] = array();
1419
				}
1420
			}
1421
		}
1422
	}
1423
 
1424
/**
1425
 * prepareLogQuery method
1426
 *
1427
 * Any prep work to log a query
1428
 *
1429
 * @param mixed $Model
1430
 * @return void
1431
 * @access protected
1432
 */
1433
	protected function _prepareLogQuery(&$Model) {
1434
		if (!$this->fullDebug) {
1435
			return false;
1436
		}
1437
		$this->_startTime = microtime(true);
1438
		$this->took = null;
1439
		$this->affected = null;
1440
		$this->error = null;
1441
		$this->numRows = null;
1442
		return true;
1443
	}
1444
 
1445
/**
1446
 * setTimeout Method
1447
 *
1448
 * Sets the MongoCursor timeout so long queries (like map / reduce) can run at will.
1449
 * Expressed in milliseconds, for an infinite timeout, set to -1
1450
 *
1451
 * @param int $ms
1452
 * @return boolean
1453
 * @access public
1454
 */
1455
	public function setTimeout($ms){
1456
		MongoCursor::$timeout = $ms;
1457
 
1458
		return true;
1459
	}
1460
 
1461
/**
1462
 * logQuery method
1463
 *
1464
 * Set timers, errors and refer to the parent
1465
 * If there are arguments passed - inject them into the query
1466
 * Show MongoIds in a copy-and-paste-into-mongo format
1467
 *
1468
 *
1469
 * @param mixed $query
1470
 * @param array $args array()
1471
 * @return void
1472
 * @access public
1473
 */
1474
	public function logQuery($query, $args = array()) {
1475
		if ($args) {
1476
			$this->_stringify($args);
1477
			$query = String::insert($query, $args);
1478
		}
1479
		$this->took = round((microtime(true) - $this->_startTime) * 1000, 0);
1480
		$this->affected = null;
1481
		if (empty($this->error['err'])) {
1482
			$this->error = $this->_db->lastError();
1483
			if (!is_scalar($this->error)) {
1484
				$this->error = json_encode($this->error);
1485
			}
1486
		}
1487
		$this->numRows = !empty($args['count'])?$args['count']:null;
1488
 
1489
		$query = preg_replace('@"ObjectId\((.*?)\)"@', 'ObjectId ("\1")', $query);
1490
		return parent::logQuery($query);
1491
	}
1492
 
1493
/**
1494
 * convertId method
1495
 *
1496
 * $conditions is used to determine if it should try to auto correct _id => array() queries
1497
 * it only appies to conditions, hence the param name
1498
 *
1499
 * @param mixed $mixed
1500
 * @param bool $conditions false
1501
 * @return void
1502
 * @access protected
1503
 */
1504
	protected function _convertId(&$mixed, $conditions = false) {
1505
		if (is_int($mixed) || ctype_digit($mixed)) {
1506
			return;
1507
		}
1508
		if (is_string($mixed)) {
1509
			if (strlen($mixed) !== 24) {
1510
				return;
1511
			}
1512
			$mixed = new MongoId($mixed);
1513
		}
1514
		if (is_array($mixed)) {
1515
			foreach($mixed as &$row) {
1516
				$this->_convertId($row, false);
1517
			}
1518
			if (!empty($mixed[0]) && $conditions) {
1519
				$mixed = array('$in' => $mixed);
1520
			}
1521
		}
1522
	}
1523
 
1524
/**
1525
 * stringify method
1526
 *
1527
 * Takes an array of args as an input and returns an array of json-encoded strings. Takes care of
1528
 * any objects the arrays might be holding (MongoID);
1529
 *
1530
 * @param array $args array()
1531
 * @param int $level 0 internal recursion counter
1532
 * @return array
1533
 * @access protected
1534
 */
1535
	protected function _stringify(&$args = array(), $level = 0) {
1536
		foreach($args as &$arg) {
1537
			if (is_array($arg)) {
1538
				$this->_stringify($arg, $level + 1);
1539
			} elseif (is_object($arg) && is_callable(array($arg, '__toString'))) {
1540
				$class = get_class($arg);
1541
				if ($class === 'MongoId') {
1542
					$arg = 'ObjectId(' . $arg->__toString() . ')';
1543
				} elseif ($class === 'MongoRegex') {
1544
					$arg = '_regexstart_' . $arg->__toString() . '_regexend_';
1545
				} else {
1546
					$arg = $class . '(' . $arg->__toString() . ')';
1547
				}
1548
			}
1549
			if ($level === 0) {
1550
				$arg = json_encode($arg);
1551
				if (strpos($arg, '_regexstart_')) {
1552
					preg_match_all('@"_regexstart_(.*?)_regexend_"@', $arg, $matches);
1553
					foreach($matches[0] as $i => $whole) {
1554
						$replace = stripslashes($matches[1][$i]);
1555
						$arg = str_replace($whole, $replace, $arg);
1556
					}
1557
				}
1558
			}
1559
		}
1560
	}
1561
 
1562
/**
1563
 * Convert automatically array('Model.field' => 'foo') to array('field' => 'foo')
1564
 *
1565
 * This introduces the limitation that you can't have a (nested) field with the same name as the model
1566
 * But it's a small price to pay to be able to use other behaviors/functionality with mongoDB
1567
 *
1568
 * @param array $args array()
1569
 * @param string $alias 'Model'
1570
 * @param bool $recurse true
1571
 * @param string $check 'key', 'value' or 'both'
1572
 * @return void
1573
 * @access protected
1574
 */
1575
	protected function _stripAlias(&$args = array(), $alias = 'Model', $recurse = true, $check = 'key') {
1576
		if (!is_array($args)) {
1577
			return;
1578
		}
1579
		$checkKey = ($check === 'key' || $check === 'both');
1580
		$checkValue = ($check === 'value' || $check === 'both');
1581
 
1582
		foreach($args as $key => &$val) {
1583
			if ($checkKey) {
1584
				if (strpos($key, $alias . '.') === 0) {
1585
					unset($args[$key]);
1586
					$key = substr($key, strlen($alias) + 1);
1587
					$args[$key] = $val;
1588
				}
1589
			}
1590
			if ($checkValue) {
1591
				if (is_string($val) && strpos($val, $alias . '.') === 0) {
1592
					$val = substr($val, strlen($alias) + 1);
1593
				}
1594
			}
1595
			if ($recurse && is_array($val)) {
1596
				$this->_stripAlias($val, $alias, true, $check);
1597
			}
1598
		}
1599
	}
1600
}
1601
 
1602
/**
1603
 * MongoDbDateFormatter method
1604
 *
1605
 * This function cannot be in the class because of the way model save is written
1606
 *
1607
 * @param mixed $date null
1608
 * @return void
1609
 * @access public
1610
 */
1611
function MongoDbDateFormatter($date = null) {
1612
	if ($date) {
1613
		return new MongoDate($date);
1614
	}
1615
	return new MongoDate();
1616
}