MyModel.php 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518
  1. <?php
  2. App::uses('Model', 'Model');
  3. App::uses('Utility', 'Tools.Utility');
  4. /**
  5. * Model enhancements for Cake2
  6. *
  7. * @author Mark Scherer
  8. * @license MIT
  9. * 2012-02-27 ms
  10. */
  11. class MyModel extends Model {
  12. public $recursive = -1;
  13. public $actsAs = array('Containable');
  14. /** Specific Stuff **/
  15. public function __construct($id = false, $table = null, $ds = null) {
  16. parent::__construct($id, $table, $ds);
  17. # enable caching
  18. if (!Configure::read('Cache.disable') && Cache::config('sql') === false) {
  19. Cache::config('sql', array(
  20. 'engine' => 'File',
  21. 'serialize' => true,
  22. 'prefix' => '',
  23. 'path' => CACHE .'sql'. DS,
  24. 'duration' => '+1 day'
  25. ));
  26. if (!file_exists(CACHE .'sql')) {
  27. mkdir(CACHE .'sql'. DS, CHOWN_PUBLIC);
  28. }
  29. }
  30. # avoiding AppModel instances instead of real Models - testing - 2011-04-03 ms
  31. if (defined('HTTP_HOST') && HTTP_HOST && !is_a($this, $this->name) && $this->displayField !== 'id' && !Configure::read('Core.disableModelInstanceNotice')) {
  32. trigger_error('AppModel instance! Expected: ' . $this->name);
  33. }
  34. }
  35. /**
  36. * The main method for any enumeration, should be called statically
  37. * Now also supports reordering/filtering
  38. *
  39. * @link http://www.dereuromark.de/2010/06/24/static-enums-or-semihardcoded-attributes/
  40. * @param string $value or array $keys or NULL for complete array result
  41. * @param array $options (actual data)
  42. * @return mixed string/array
  43. * static enums
  44. * 2009-11-05 ms
  45. */
  46. public static function enum($value, $options, $default = '') {
  47. if ($value !== null && !is_array($value)) {
  48. if (array_key_exists($value, $options)) {
  49. return $options[$value];
  50. }
  51. return $default;
  52. } elseif ($value !== null) {
  53. $newOptions = array();
  54. foreach ($value as $v) {
  55. $newOptions[$v] = $options[$v];
  56. }
  57. return $newOptions;
  58. }
  59. return $options;
  60. }
  61. /**
  62. * Catch database errors before it’s too late
  63. * //TODO: testing
  64. * 2010-11-04 ms
  65. */
  66. public function onError() {
  67. $err = $this->lastError();
  68. if (!empty($err)) {
  69. $this->log($err, 'sql_error');
  70. } else {
  71. $this->log('unknown error', 'sql_error');
  72. }
  73. if (!empty($this->data)) {
  74. $data = $this->data;
  75. } elseif ($this->id) {
  76. $data = 'id ' . $this->id;
  77. } else {
  78. $data = 'no data';
  79. }
  80. $data .= ' (' . env('REDIRECT_URL') . ')';
  81. $this->log($data, 'sql_error');
  82. }
  83. /**
  84. * @return string Error message with error number
  85. * 2010-11-06 ms
  86. */
  87. public function lastError() {
  88. $db = $this->getDataSource();
  89. return $db->lastError();
  90. }
  91. /**
  92. * combine virtual fields with fields values of find()
  93. * USAGE:
  94. * $this->Model->find('all', array('fields' => $this->Model->virtualFields('full_name')));
  95. * Also adds the field to the virtualFields array of the model (for correct result)
  96. * TODO: adding of fields only temperory!
  97. * @param array $virtualFields to include
  98. * 2011-10-13 ms
  99. */
  100. public function virtualFields($fields = array()) {
  101. $res = array();
  102. foreach ((array)$fields as $field => $sql) {
  103. if (is_int($field)) {
  104. $field = $sql;
  105. $sql = null;
  106. }
  107. $plugin = $model = null;
  108. if (($pos = strrpos($field, '.')) !== false) {
  109. $model = substr($field, 0, $pos);
  110. $field = substr($field, $pos+1);
  111. if (($pos = strrpos($model, '.')) !== false) {
  112. list($plugin, $model) = pluginSplit($model);
  113. }
  114. }
  115. if (empty($model)) {
  116. $model = $this->alias;
  117. if ($sql === null) {
  118. $sql = $this->virtualFields[$field];
  119. } else {
  120. $this->virtualFields[$field] = $sql;
  121. }
  122. } else {
  123. if (!isset($this->$model)) {
  124. $fullModelName = ($plugin ? $plugin.'.' : '') . $model;
  125. $this->$model = ClassRegistry::init($fullModelName);
  126. }
  127. if ($sql === null) {
  128. $sql = $this->$model->virtualFields[$field];
  129. } else {
  130. $this->$model->virtualFields[$field] = $sql;
  131. }
  132. }
  133. $res[] = $sql.' AS '.$model.'__'.$field;
  134. }
  135. return $res;
  136. }
  137. /**
  138. * HIGHLY EXPERIMENTAL
  139. * manually escape value for updateAll() etc
  140. * 2011-06-27 ms
  141. */
  142. public function escapeValue($value) {
  143. if (is_numeric($value)) {
  144. return $value;
  145. }
  146. if (is_bool($value)) {
  147. return (int)$value;
  148. }
  149. return "'" . $value . "'";
  150. }
  151. /**
  152. * HIGHLY EXPERIMENTAL
  153. * @see http://cakephp.lighthouseapp.com/projects/42648/tickets/1799-model-should-have-escapefield-method
  154. * 2011-07-05 ms
  155. */
  156. public function value($content) {
  157. $db = $this->getDatasource();
  158. return $db->value($content);
  159. }
  160. /**
  161. * TODO: move to behavior (Incremental)
  162. * @param mixed id (single string)
  163. * @param options:
  164. * - step (defaults to 1)
  165. * - current (if none it will get it from db)
  166. * - reset (if true, it will be set to 0)
  167. * - field (defaults to 'count')
  168. * - modify (if true if will affect modified timestamp)
  169. * - timestampField (if provided it will be filled with NOW())
  170. * 2010-06-08 ms
  171. */
  172. public function up($id, $customOptions = array()) {
  173. $step = 1;
  174. if (isset($customOptions['step'])) {
  175. $step = $customOptions['step'];
  176. }
  177. $field = 'count';
  178. if (isset($customOptions['field'])) {
  179. $field = $customOptions['field'];
  180. }
  181. if (isset($customOptions['reset'])) {
  182. $currentValue = $step = 0;
  183. } elseif (!isset($customOptions['current'])) {
  184. $currentValue = $this->field($field, array($this->alias.'.id'=>$id));
  185. if ($currentValue === false) {
  186. return false;
  187. }
  188. } else {
  189. $currentValue = $customOptions['current'];
  190. }
  191. $value = (int)$currentValue + (int)$step;
  192. $data = array($field=>$value);
  193. if (empty($customOptions['modify'])) {
  194. $data['modified'] = false;
  195. }
  196. if (!empty($customOptions['timestampField'])) {
  197. $data[$customOptions['timestampField']] = date(FORMAT_DB_DATETIME);
  198. }
  199. $this->id = $id;
  200. return $this->save($data, false);
  201. }
  202. /**
  203. * improve paginate count for "normal queries"
  204. * 2011-04-11 ms
  205. */
  206. public function _paginateCount($conditions = null, $recursive = -1, $extra = array()) {
  207. $conditions = compact('conditions');
  208. if ($recursive != $this->recursive) {
  209. $conditions['recursive'] = $recursive;
  210. }
  211. if ($recursive == -1) {
  212. $extra['contain'] = array();
  213. }
  214. return $this->find('count', array_merge($conditions, $extra));
  215. }
  216. /**
  217. * Retourne le prochain id auto-increment d'une table
  218. * UUIDs will return false
  219. * @return int next auto increment value or False on failure
  220. */
  221. public function getNextAutoIncrement() {
  222. $next_increment = 0;
  223. $query = "SHOW TABLE STATUS WHERE name = '" . $this->tablePrefix . $this->table . "'";
  224. $result = $this->query($query);
  225. if (!isset($result[0]['TABLES']['Auto_increment'])) {
  226. return false;
  227. }
  228. $next_increment = (int)$result[0]['TABLES']['Auto_increment'];
  229. return $next_increment;
  230. }
  231. /**
  232. * workaround for a cake bug which sets empty fields to NULL in Model::set()
  233. * we cannot use if (isset() && empty()) statements without this fix
  234. * @param array $fields (which are supposed to be present in $this->data[$this->alias])
  235. * @param bool $force (if init should be forced, otherwise only if array_key exists)
  236. * 2011-03-06 ms
  237. */
  238. public function init($fields = array(), $force = false) {
  239. foreach ($fields as $field) {
  240. if ($force || array_key_exists($field, $this->data[$this->alias])) {
  241. if (!isset($this->data[$this->alias][$field])) {
  242. $this->data[$this->alias][$field] = '';
  243. }
  244. }
  245. }
  246. }
  247. public function saveAll($data = null, $options = array()) {
  248. $options['atomic'] = false;
  249. return parent::saveAll($data, $options);
  250. }
  251. /**
  252. * enables HABTM-Validation
  253. * e.g. with
  254. * 'rule' => array('multiple',array('min' => 2))
  255. * 2010-01-14 ms
  256. */
  257. public function beforeValidate($options = array()) {
  258. foreach ($this->hasAndBelongsToMany as $k => $v) {
  259. if (isset($this->data[$k][$k])) {
  260. $this->data[$this->alias][$k] = $this->data[$k][$k];
  261. }
  262. }
  263. parent::beforeValidate($options);
  264. }
  265. /**
  266. * @param params
  267. * - key: functioName or other key used
  268. * @static
  269. * 2010-12-02 ms
  270. */
  271. public function deleteCache($key) {
  272. //extract($params);
  273. $key = Inflector::underscore($key);
  274. if (!empty($key)) {
  275. Cache::delete(strtolower(Inflector::underscore($this->alias)) . '__' . $key, 'sql');
  276. } else {
  277. Cache::clear(false, 'sql');
  278. }
  279. return true;
  280. }
  281. /**
  282. * Makes a subquery
  283. * @link http://bakery.cakephp.org/articles/lucaswxp/2011/02/11/easy_and_simple_subquery_cakephp
  284. *
  285. * @param string|array $type The type o the query (only available 'count') or the $options
  286. * @param string|array $options The options array or $alias in case of $type be a array
  287. * @param string $alias You can use this intead of $options['alias'] if you want
  288. * @return string $result sql snippet of the query to run
  289. * 2011-07-05 ms
  290. */
  291. public function subquery($type, $options = null, $alias = null) {
  292. $fields = array();
  293. if (is_string($type)) {
  294. $isString = true;
  295. } else {
  296. $alias = $options;
  297. $options = $type;
  298. }
  299. if ($alias === null) {
  300. $alias = 'Sub' . $this->alias . '';
  301. }
  302. if (isset($isString)) {
  303. switch ($type) {
  304. case 'count':
  305. $fields = array('COUNT(*)');
  306. break;
  307. }
  308. }
  309. $dbo = $this->getDataSource();
  310. $default = array(
  311. 'fields' => $fields,
  312. 'table' => $dbo->fullTableName($this),
  313. 'alias' => $alias,
  314. 'limit' => null,
  315. 'offset' => null,
  316. 'joins' => array(),
  317. 'conditions' => array(),
  318. 'order' => null,
  319. 'group' => null
  320. );
  321. $params = array_merge($default, $options);
  322. $subQuery = $dbo->buildStatement($params, $this);
  323. return $subQuery;
  324. }
  325. /**
  326. * Wrapper find() to cache sql queries.
  327. *
  328. * @access public
  329. * @param array $conditions
  330. * @param array $fields
  331. * @param string $order
  332. * @param string $recursive
  333. * @return array
  334. * 2010-12-02 ms
  335. */
  336. public function find($type = null, $query = array()) {
  337. # reset/delete
  338. if (!empty($query['reset'])) {
  339. if (!empty($query['cache'])) {
  340. if (is_array($query['cache'])) {
  341. $key = $query['cache'][0];
  342. } else {
  343. $key = $query['cache'];
  344. if ($key === true) {
  345. $backtrace = debug_backtrace();
  346. $key = $backtrace[1]['function'];
  347. }
  348. }
  349. $this->deleteCache($key);
  350. }
  351. }
  352. # custom fixes
  353. if (is_string($type)) {
  354. switch ($type) {
  355. case 'count':
  356. if (isset($query['fields'])) {
  357. unset($query['fields']);
  358. }
  359. break;
  360. default:
  361. }
  362. }
  363. # having and group clauses enhancement
  364. if (is_array($query) && !empty($query['having']) && !empty($query['group'])) {
  365. if (!is_array($query['group'])) {
  366. $query['group'] = array($query['group']);
  367. }
  368. $ds = $this->getDataSource();
  369. $having = $ds->conditions($query['having'], true, false);
  370. $query['group'][count($query['group']) - 1] .= " HAVING $having";
  371. } elseif (is_array($query) && !empty($query['having'])) {
  372. $ds = $this->getDataSource();
  373. $having = $ds->conditions($query['having'], true, false);
  374. $query['conditions'][] = '1=1 HAVING '.$having;
  375. }
  376. # find
  377. if (!Configure::read('Cache.disable') && Configure::read('Cache.check') && !empty($query['cache'])) {
  378. if (is_array($query['cache'])) {
  379. $key = $query['cache'][0];
  380. $expires = DAY;
  381. if (!empty($query['cache'][1])) {
  382. $expires = $query['cache'][1];
  383. }
  384. } else {
  385. $key = $query['cache'];
  386. if ($key === true) {
  387. $backtrace = debug_backtrace();
  388. $key = $backtrace[1]['function'];
  389. }
  390. $expires = DAY;
  391. }
  392. $options = array('prefix' => strtolower(Inflector::underscore($this->alias)) . '__', );
  393. if (!empty($expires)) {
  394. $options['duration'] = $expires;
  395. }
  396. if (!Configure::read('Cache.disable')) {
  397. Cache::config('sql', $options);
  398. $key = Inflector::underscore($key);
  399. $results = Cache::read($key, 'sql');
  400. }
  401. if ($results === null) {
  402. $results = parent::find($type, $query);
  403. Cache::write($key, $results, 'sql');
  404. }
  405. return $results;
  406. }
  407. # Without caching
  408. return parent::find($type, $query);
  409. }
  410. /*
  411. public function _findCount($state, $query, $results = array()) {
  412. if (isset($query['fields'])) {
  413. unset($query['fields']);
  414. }
  415. pr($results);
  416. return parent::_findCount($state, $query, $results = array());
  417. }
  418. */
  419. /**
  420. * This code will add formatted list functionallity to find you can easy replace the $this->Model->find('list'); with $this->Model->find('formattedlist', array('fields' => array('Model.id', 'Model.field1', 'Model.field2', 'Model.field3'), 'format' => '%s-%s %s')); and get option tag output of: Model.field1-Model.field2 Model.field3. Even better part is being able to setup your own format for the output!
  421. * @see http://bakery.cakephp.org/articles/view/add-formatted-lists-to-your-appmodel
  422. * @deprecated
  423. * added Caching
  424. * 2009-12-27 ms
  425. */
  426. protected function _find($type, $options = array()) {
  427. $res = false; // $this->_getCachedResults($type, $options);
  428. if ($res === false) {
  429. if (isset($options['cache'])) {
  430. unset($options['cache']);
  431. }
  432. if (!isset($options['recursive'])) {
  433. //$options['recursive'] = -1;
  434. }
  435. switch ($type) {
  436. # @see http://bakery.cakephp.org/deu/articles/nate/2010/10/10/quick-tipp_-_doing_ad-hoc-joins_bei_model_find
  437. case 'matches':
  438. if (!isset($options['joins'])) {
  439. $options['joins'] = array();
  440. }
  441. if (!isset($options['model']) || !isset($options['scope'])) {
  442. break;
  443. }
  444. $assoc = $this->hasAndBelongsToMany[$options['model']];
  445. $bind = "{$assoc['with']}.{$assoc['foreignKey']} = {$this->alias}.{$this->primaryKey}";
  446. $options['joins'][] = array(
  447. 'table' => $assoc['joinTable'],
  448. 'alias' => $assoc['with'],
  449. 'type' => 'inner',
  450. 'foreignKey' => false,
  451. 'conditions'=> array($bind)
  452. );
  453. $bind = $options['model'] . '.' . $this->{$options['model']}->primaryKey . ' = ';
  454. $bind .= "{$assoc['with']}.{$assoc['associationForeignKey']}";
  455. $options['joins'][] = array(
  456. 'table' => $this->{$options['model']}->table,
  457. 'alias' => $options['model'],
  458. 'type' => 'inner',
  459. 'foreignKey' => false,
  460. 'conditions'=> array($bind) + (array)$options['scope'],
  461. );
  462. unset($options['model'], $options['scope']);
  463. $type = 'all';
  464. break;
  465. # probably deprecated since "virtual fields" in 1.3
  466. case 'formattedlist':
  467. if (!isset($options['fields']) || count($options['fields']) < 3) {
  468. $res = parent::find('list', $options);
  469. break;
  470. }
  471. $this->recursive = -1;
  472. //setup formating
  473. $format = '';
  474. if (!isset($options['format'])) {
  475. for ($i = 0; $i < (count($options['fields']) - 1); $i++) $format .= '%s ';
  476. $format = substr($format, 0, -1);
  477. } else {
  478. $format = $options['format'];
  479. }
  480. //get data
  481. $list = parent::find('all', $options);
  482. // remove model alias from strings to only get field names
  483. $tmpPath2[] = $format;
  484. for ($i = 1; $i <= (count($options['fields']) - 1); $i++) {
  485. $field[$i] = str_replace($this->alias . '.', '', $options['fields'][$i]);
  486. $tmpPath2[] = '{n}.' . $this->alias . '.' . $field[$i];
  487. }
  488. //do the magic?? read the code...
  489. $res = Set::combine($list, '{n}.' . $this->alias . '.' . $this->primaryKey, $tmpPath2);
  490. break;
  491. default:
  492. $res = parent::find($type, $options);
  493. break;
  494. }
  495. if (!empty($this->useCache)) {
  496. Cache::write($this->cacheName, $res, $this->cacheConfig);
  497. if (Configure::read('debug') > 0) {
  498. $this->log('WRITE (' . $this->cacheConfig . '): ' . $this->cacheName, 'cache');
  499. }
  500. }
  501. } else {
  502. if (Configure::read('debug') > 0) {
  503. $this->log('READ (' . $this->cacheConfig . '): ' . $this->cacheName, 'cache');
  504. }
  505. }
  506. return $res;
  507. }
  508. /*
  509. USAGE of formattetlist:
  510. $this->Model->find('formattedlist',
  511. array(
  512. 'fields'=>array(
  513. 'Model.id', // allows start with the value="" tags field
  514. 'Model.field1', // then put them in order of how you want the format to output.
  515. 'Model.field2',
  516. 'Model.field3',
  517. 'Model.field4',
  518. 'Model.field5',
  519. ),
  520. 'format'=>'%s-%s%s %s%s'
  521. )
  522. );
  523. */
  524. /**
  525. * @deprecated
  526. */
  527. public function _getCachedResults($type, $options) {
  528. $this->useCache = true;
  529. if (!is_array($options) || empty($options['cache']) || Configure::read('debug') > 0 && !(Configure::read('Debug.override'))) {
  530. $this->useCache = false;
  531. return false;
  532. }
  533. if ($options['cache'] === true) {
  534. $this->cacheName = $this->alias . '_' . sha1($type . serialize($options));
  535. } else {
  536. /*
  537. if (!isset($options['cache']['name'])) {
  538. return false;
  539. }
  540. */
  541. $this->cacheName = $this->alias . '_' . sha1($type . serialize($options));
  542. $this->cacheConfig = $options['cache'];
  543. //$this->cacheName = $this->alias . '_' . $type . '_' . $options['cache'];
  544. //$this->cacheConfig = isset($options['cache']['config']) ? $options['cache']['config'] : 'default';
  545. }
  546. $results = Cache::read($this->cacheName, $this->cacheConfig);
  547. return $results;
  548. }
  549. /*
  550. neighbor find problem:
  551. This means it will sort the results on Model.created ASC and DESC.
  552. However, in certain situations you would like to order on more than one
  553. field. For example, on a rating and a uploaddate. Requirements could look
  554. like: Get next en previous record of a certain Model based on the top
  555. rated. When the rating is equal those should be ordered on creation date.
  556. I suggest something similar to:
  557. $this->Movie->find('neighbors', array(
  558. 'scope' => array(
  559. array(
  560. 'field' => 'rating',
  561. 'order' => 'DESC',
  562. 'value' => 4.85
  563. ),
  564. array(
  565. 'field' => 'created',
  566. 'order' => 'DESC',
  567. 'value' => '2009-05-26 06:20:03'
  568. )
  569. )
  570. 'conditions' => array(
  571. 'approved' => true,
  572. 'processed' => true
  573. )
  574. */
  575. /**
  576. * core-fix for multiple sort orders
  577. * @param addiotional 'scope'=>array(field,order) - value is retrieved by (submitted) primary key
  578. * 2009-07-25 ms
  579. * TODO: fix it
  580. * TODO: rename it to just find() or integrate it there
  581. */
  582. public function findNeighbors($type, $options = array()) {
  583. if ($type == 'neighbors' && isset($options['scope'])) {
  584. $type == 'neighborsTry';
  585. }
  586. switch ($type) {
  587. case 'neighborsTry': # use own implementation
  588. return $xxx; # TODO: implement
  589. break;
  590. default:
  591. return parent::find($type, $options);
  592. break;
  593. }
  594. }
  595. /**
  596. * @param mixed $id: id only, or request array
  597. * @param array $options
  598. * - filter: open/closed/none
  599. * - field (sortField, if not id)
  600. * - reverse: sortDirection (0=normalAsc/1=reverseDesc)
  601. * - displayField: ($this->displayField, if empty)
  602. * @param array qryOptions
  603. * - recursive (defaults to -1)
  604. * TODO: try to use core function, TRY TO ALLOW MULTIPLE SORT FIELDS
  605. */
  606. public function neighbors($id = null, $options = array(), $qryOptions = array()) {
  607. $sortField = (!empty($options['field']) ? $options['field'] : 'created');
  608. $normalDirection = (!empty($options['reverse']) ? false : true);
  609. $sortDirWord = $normalDirection ? array('ASC', 'DESC') : array('DESC', 'ASC');
  610. $sortDirSymb = $normalDirection ? array('>=', '<=') : array('<=', '>=');
  611. $displayField = (!empty($options['displayField']) ? $options['displayField'] : $this->displayField);
  612. if (is_array($id)) {
  613. $data = $id;
  614. $id = $data[$this->alias]['id'];
  615. } elseif ($id === null) {
  616. $id = $this->id;
  617. }
  618. if (!empty($id)) {
  619. $data = $this->find('first', array('conditions' => array('id' => $id), 'contain' => array()));
  620. }
  621. if (empty($id) || empty($data) || empty($data[$this->alias][$sortField])) {
  622. return false;
  623. } else {
  624. $field = $data[$this->alias][$sortField];
  625. }
  626. $findOptions = array('recursive' => -1);
  627. if (isset($qryOptions['recursive'])) {
  628. $findOptions['recursive'] = $qryOptions['recursive'];
  629. }
  630. if (isset($qryOptions['contain'])) {
  631. $findOptions['contain'] = $qryOptions['contain'];
  632. }
  633. $findOptions['fields'] = array($this->alias . '.id', $this->alias . '.' . $displayField);
  634. $findOptions['conditions'][$this->alias . '.id !='] = $id;
  635. # //TODO: take out
  636. if (!empty($options['filter']) && $options['filter'] == REQUEST_STATUS_FILTER_OPEN) {
  637. $findOptions['conditions'][$this->alias . '.status <'] = REQUEST_STATUS_DECLINED;
  638. } elseif (!empty($options['filter']) && $options['filter'] == REQUEST_STATUS_FILTER_CLOSED) {
  639. $findOptions['conditions'][$this->alias . '.status >='] = REQUEST_STATUS_DECLINED;
  640. }
  641. $return = array();
  642. if (!empty($qryOptions['conditions'])) {
  643. $findOptions['conditions'] = Set::merge($findOptions['conditions'], $qryOptions['conditions']);
  644. }
  645. $options = $findOptions;
  646. $options['conditions'] = Set::merge($options['conditions'], array($this->alias . '.' . $sortField . ' ' . $sortDirSymb[1] => $field));
  647. $options['order'] = array($this->alias . '.' . $sortField . '' => $sortDirWord[1]);
  648. $this->id = $id;
  649. $return['prev'] = $this->find('first', $options);
  650. $options = $findOptions;
  651. $options['conditions'] = Set::merge($options['conditions'], array($this->alias . '.' . $sortField . ' ' . $sortDirSymb[0] => $field));
  652. $options['order'] = array($this->alias . '.' . $sortField . '' => $sortDirWord[0]); // ??? why 0 instead of 1
  653. $this->id = $id;
  654. $return['next'] = $this->find('first', $options);
  655. return $return;
  656. }
  657. /** Validation Functions **/
  658. /**
  659. * validates a primary or foreign key depending on the current schema data for this field
  660. * recognizes uuid (char36) and aiid (int10 unsigned) - not yet mixed (varchar36)
  661. * more useful than using numeric or notEmpty which are type specific
  662. * @param array $data
  663. * @param array $options
  664. * - allowEmpty
  665. * 2011-06-21 ms
  666. */
  667. public function validateKey($data = array(), $options = array()) {
  668. $key = array_shift(array_keys($data));
  669. $value = array_shift($data);
  670. $schema = $this->schema($key);
  671. if (!$schema) {
  672. return true;
  673. }
  674. $defaults = array(
  675. 'allowEmpty' => false,
  676. );
  677. $options = am($defaults, $options);
  678. if ($schema['type'] != 'integer') {
  679. if ($options['allowEmpty'] && $value === '') {
  680. return true;
  681. }
  682. return Validation::uuid($value);
  683. }
  684. if ($options['allowEmpty'] && $value === 0) {
  685. return true;
  686. }
  687. return is_numeric($value) && (int)$value == $value && $value > 0;
  688. }
  689. /**
  690. * checks if the passed enum value is valid
  691. * 2010-02-09 ms
  692. */
  693. public function validateEnum($field = array(), $enum = null, $additionalKeys = array()) {
  694. $valueKey = array_shift(array_keys($field)); # auto-retrieve
  695. $value = $field[$valueKey];
  696. $keys = array();
  697. if ($enum === true) {
  698. $enum = $valueKey;
  699. }
  700. if ($enum !== null) {
  701. if (!method_exists($this, $enum)) {
  702. trigger_error('Enum method \'' . $enum . '()\' not exists', E_USER_ERROR);
  703. return false;
  704. }
  705. //TODO: make static
  706. $keys = $this->{$enum}();
  707. }
  708. $keys = array_merge($additionalKeys, array_keys($keys));
  709. if (!empty($keys) && in_array($value, $keys)) {
  710. return true;
  711. }
  712. return false;
  713. }
  714. /**
  715. * checks if the content of 2 fields are equal
  716. * Does not check on empty fields! Return TRUE even if both are empty (secure against empty in another rule)!
  717. * 2009-01-22 ms
  718. */
  719. public function validateIdentical($data = array(), $compareWith = null, $options = array()) {
  720. if (is_array($data)) {
  721. $value = array_shift($data);
  722. } else {
  723. $value = $data;
  724. }
  725. $compareValue = $this->data[$this->alias][$compareWith];
  726. $matching = array('string' => 'string', 'int' => 'integer', 'float' => 'float', 'bool' => 'boolean');
  727. if (!empty($options['cast']) && array_key_exists($options['cast'], $matching)) {
  728. # cast values to string/int/float/bool if desired
  729. settype($compareValue, $matching[$options['cast']]);
  730. settype($value, $matching[$options['cast']]);
  731. }
  732. return ($compareValue === $value);
  733. }
  734. /**
  735. * checks a record, if it is unique - depending on other fields in this table (transfered as array)
  736. * example in model: 'rule' => array ('validateUnique',array('belongs_to_table_id','some_id','user_id')),
  737. * if all keys (of the array transferred) match a record, return false, otherwise true
  738. * @param ARRAY other fields
  739. * TODO: add possibity of deep nested validation (User -> Comment -> CommentCategory: UNIQUE comment_id, Comment.user_id)
  740. * 2010-01-30 ms
  741. */
  742. public function validateUnique($data, $fields = array(), $options = array()) {
  743. $id = (!empty($this->data[$this->alias]['id']) ? $this->data[$this->alias]['id'] : 0);
  744. foreach ($data as $key => $value) {
  745. $fieldName = $key;
  746. $fieldValue = $value; // equals: $this->data[$this->alias][$fieldName]
  747. }
  748. if (empty($fieldName) || empty($fieldValue)) { // return true, if nothing is transfered (check on that first)
  749. return true;
  750. }
  751. $conditions = array($this->alias . '.' . $fieldName => $fieldValue, // Model.field => $this->data['Model']['field']
  752. $this->alias . '.id !=' => $id, );
  753. # careful, if fields is not manually filled, the options will be the second param!!! big problem...
  754. foreach ((array )$fields as $dependingField) {
  755. if (isset($this->data[$this->alias][$dependingField])) { // add ONLY if some content is transfered (check on that first!)
  756. $conditions[$this->alias . '.' . $dependingField] = $this->data[$this->alias][$dependingField];
  757. } elseif (!empty($this->data['Validation'][$dependingField])) { // add ONLY if some content is transfered (check on that first!
  758. $conditions[$this->alias . '.' . $dependingField] = $this->data['Validation'][$dependingField];
  759. } elseif (!empty($id)) {
  760. # manual query! (only possible on edit)
  761. $res = $this->find('first', array('fields' => array($this->alias.'.'.$dependingField), 'conditions' => array($this->alias.'.id' => $this->data[$this->alias]['id'])));
  762. if (!empty($res)) {
  763. $conditions[$this->alias . '.' . $dependingField] = $res[$this->alias][$dependingField];
  764. }
  765. }
  766. }
  767. $this->recursive = -1;
  768. if (count($conditions) > 2) {
  769. $this->recursive = 0;
  770. }
  771. $res = $this->find('first', array('fields' => array($this->alias . '.id'), 'conditions' => $conditions));
  772. if (!empty($res)) {
  773. return false;
  774. }
  775. return true;
  776. }
  777. /**
  778. * @param array $data
  779. * @param array $options
  780. * - scope (array of other fields as scope - isUnique dependent on other fields of the table)
  781. * - batch (defaults to true, remembers previous values in order to validate batch imports)
  782. * example in model: 'rule' => array ('validateUniqueExt', array('scope'=>array('belongs_to_table_id','some_id','user_id'))),
  783. * http://groups.google.com/group/cake-php/browse_thread/thread/880ee963456739ec
  784. * //TODO: test!!!
  785. * 2011-03-27 ms
  786. */
  787. public function validateUniqueExt($data, $options = array()) {
  788. foreach ($data as $key => $value) {
  789. $fieldName = $key;
  790. $fieldValue = $value;
  791. }
  792. $defaults = array('batch' => true, 'scope' => array());
  793. $options = array_merge($defaults, $options);
  794. # for batch
  795. if ($options['batch'] !== false && !empty($this->batchRecords)) {
  796. if (array_key_exists($value, $this->batchRecords[$fieldName])) {
  797. return $options['scope'] === $this->batchRecords[$fieldName][$value];
  798. }
  799. }
  800. # continue with validation
  801. if (!$this->validateUnique($data, $options['scope'])) {
  802. return false;
  803. }
  804. # for batch
  805. if ($options['batch'] !== false) {
  806. if (!isset($this->batchRecords)) {
  807. $this->batchRecords = array();
  808. }
  809. $this->batchRecords[$fieldName][$value] = $scope;
  810. }
  811. return true;
  812. }
  813. /**
  814. * checks if a url is valid AND accessable (returns false otherwise)
  815. * @param array/string $data: full url(!) starting with http://...
  816. * @options
  817. * - allowEmpty TRUE/FALSE (TRUE: if empty => return TRUE)
  818. * - required TRUE/FALSE (TRUE: overrides allowEmpty)
  819. * - autoComplete (default: TRUE)
  820. * - deep (default: TRUE)
  821. * 2010-10-18 ms
  822. */
  823. public function validateUrl($data, $options = array()) {
  824. //$arguments = func_get_args();
  825. if (is_array($data)) {
  826. $url = array_shift($data);
  827. } else {
  828. $url = $data;
  829. }
  830. if (empty($url)) {
  831. if (!empty($options['allowEmpty']) && empty($options['required'])) {
  832. return true;
  833. }
  834. return false;
  835. }
  836. if (!isset($options['autoComplete']) || $options['autoComplete'] !== false) {
  837. $url = $this->_autoCompleteUrl($url);
  838. }
  839. if (!isset($options['strict']) || $options['strict'] !== false) {
  840. $options['strict'] = true;
  841. }
  842. # validation
  843. if (!Validation::url($url, $options['strict']) && env('REMOTE_ADDR') != '127.0.0.1') {
  844. return false;
  845. }
  846. # same domain?
  847. if (!empty($options['sameDomain']) && !empty($_SERVER['HTTP_HOST'])) {
  848. $is = parse_url($url, PHP_URL_HOST);
  849. $expected = $_SERVER['HTTP_HOST'];
  850. if (mb_strtolower($is) !== mb_strtolower($expected)) {
  851. return false;
  852. }
  853. }
  854. if (isset($options['deep']) && $options['deep'] === false) {
  855. return true;
  856. }
  857. return $this->_validUrl($url);
  858. }
  859. public function _autoCompleteUrl($url) {
  860. if (mb_strpos($url, '://') === false && mb_strpos($url, 'www.') === 0) {
  861. $url = 'http://' . $url;
  862. } elseif (mb_strpos($url, '/') === 0) {
  863. $url = Router::url($url, true);
  864. }
  865. return $url;
  866. }
  867. /**
  868. * checks if a url is valid
  869. * @param string url
  870. * 2009-02-27 ms
  871. */
  872. public function _validUrl($url = null) {
  873. App::import('Component', 'Tools.Common');
  874. $headers = Utility::getHeaderFromUrl($url);
  875. if ($headers !== false) {
  876. $headers = implode("\n", $headers);
  877. return ((bool)preg_match('#^HTTP/.*\s+[(200|301|302)]+\s#i', $headers) && !(bool)preg_match('#^HTTP/.*\s+[(404|999)]+\s#i', $headers));
  878. }
  879. return false;
  880. }
  881. /**
  882. * Validation of DateTime Fields (both Date and Time together)
  883. * @param options
  884. * - dateFormat (defaults to 'ymd')
  885. * - allowEmpty
  886. * - after/before (fieldName to validate against)
  887. * - min/max (defaults to >= 1 - at least 1 minute apart)
  888. * 2011-03-02 ms
  889. */
  890. public function validateDateTime($data, $options = array()) {
  891. $format = !empty($options['dateFormat']) ? $options['dateFormat'] : 'ymd';
  892. if (is_array($data)) {
  893. $value = array_shift($data);
  894. } else {
  895. $value = $data;
  896. }
  897. $dateTime = explode(' ', trim($value), 2);
  898. $date = $dateTime[0];
  899. $time = (!empty($dateTime[1]) ? $dateTime[1] : '');
  900. if (!empty($options['allowEmpty']) && (empty($date) && empty($time) || $date == DEFAULT_DATE && $time == DEFAULT_TIME || $date == DEFAULT_DATE && empty($time))) {
  901. return true;
  902. }
  903. /*
  904. if ($this->validateDate($date, $options) && $this->validateTime($time, $options)) {
  905. return true;
  906. }
  907. */
  908. if (Validation::date($date, $format) && Validation::time($time)) {
  909. # after/before?
  910. $minutes = isset($options['min']) ? $options['min'] : 1;
  911. if (!empty($options['after']) && isset($this->data[$this->alias][$options['after']])) {
  912. if (strtotime($this->data[$this->alias][$options['after']]) > strtotime($value) - $minutes) {
  913. return false;
  914. }
  915. }
  916. if (!empty($options['before']) && isset($this->data[$this->alias][$options['before']])) {
  917. if (strtotime($this->data[$this->alias][$options['before']]) < strtotime($value) + $minutes) {
  918. return false;
  919. }
  920. }
  921. return true;
  922. }
  923. return false;
  924. }
  925. /**
  926. * Validation of Date Fields (as the core one is buggy!!!)
  927. * @param options
  928. * - dateFormat (defaults to 'ymd')
  929. * - allowEmpty
  930. * - after/before (fieldName to validate against)
  931. * - min (defaults to 0 - equal is OK too)
  932. * 2011-03-02 ms
  933. */
  934. public function validateDate($data, $options = array()) {
  935. $format = !empty($options['format']) ? $options['format'] : 'ymd';
  936. if (is_array($data)) {
  937. $value = array_shift($data);
  938. } else {
  939. $value = $data;
  940. }
  941. $dateTime = explode(' ', trim($value), 2);
  942. $date = $dateTime[0];
  943. if (!empty($options['allowEmpty']) && (empty($date) || $date == DEFAULT_DATE)) {
  944. return true;
  945. }
  946. if (Validation::date($date, $format)) {
  947. # after/before?
  948. $days = !empty($options['min']) ? $options['min'] : 0;
  949. if (!empty($options['after']) && isset($this->data[$this->alias][$options['after']])) {
  950. if ($this->data[$this->alias][$options['after']] > date(FORMAT_DB_DATE, strtotime($date) - $days * DAY)) {
  951. return false;
  952. }
  953. }
  954. if (!empty($options['before']) && isset($this->data[$this->alias][$options['before']])) {
  955. if ($this->data[$this->alias][$options['before']] < date(FORMAT_DB_DATE, strtotime($date) + $days * DAY)) {
  956. return false;
  957. }
  958. }
  959. return true;
  960. }
  961. return false;
  962. }
  963. /**
  964. * @param options
  965. * - timeFormat (defaults to 'hms')
  966. * - allowEmpty
  967. * - after/before (fieldName to validate against)
  968. * - min/max (defaults to >= 1 - at least 1 minute apart)
  969. * 2011-03-02 ms
  970. */
  971. public function validateTime($data, $options = array()) {
  972. if (is_array($data)) {
  973. $value = array_shift($data);
  974. } else {
  975. $value = $data;
  976. }
  977. $dateTime = explode(' ', trim($value), 2);
  978. $value = array_pop($dateTime);
  979. if (Validation::time($value)) {
  980. # after/before?
  981. if (!empty($options['after']) && isset($this->data[$this->alias][$options['after']])) {
  982. if ($this->data[$this->alias][$options['after']] >= $value) {
  983. return false;
  984. }
  985. }
  986. if (!empty($options['before']) && isset($this->data[$this->alias][$options['before']])) {
  987. if ($this->data[$this->alias][$options['before']] <= $value) {
  988. return false;
  989. }
  990. }
  991. return true;
  992. }
  993. return false;
  994. }
  995. //TODO
  996. /**
  997. * Validation of Date Fields (>= minDate && <= maxDate)
  998. * @param options
  999. * - min/max (TODO!!)
  1000. * 2010-01-20 ms
  1001. */
  1002. public function validateDateRange($data, $options = array()) {
  1003. }
  1004. //TODO
  1005. /**
  1006. * Validation of Time Fields (>= minTime && <= maxTime)
  1007. * @param options
  1008. * - min/max (TODO!!)
  1009. * 2010-01-20 ms
  1010. */
  1011. public function validateTimeRange($data, $options = array()) {
  1012. }
  1013. /**
  1014. * model validation rule for email addresses
  1015. * 2010-01-14 ms
  1016. */
  1017. public function validateUndisposable($data, $proceed = false) {
  1018. $email = array_shift($data);
  1019. if (empty($email)) {
  1020. return true;
  1021. }
  1022. return $this->isUndisposableEmail($email, false, $proceed);
  1023. }
  1024. /**
  1025. * NOW: can be set to work offline only (if server is down etc)
  1026. *
  1027. * checks if a email is not from a garbige hoster
  1028. * @param string email (neccessary)
  1029. * @return boolean true if valid, else false
  1030. * 2009-03-09 ms
  1031. */
  1032. public function isUndisposableEmail($email, $onlineMode = false, $proceed = false) {
  1033. if (!isset($this->UndisposableEmail)) {
  1034. App::import('Vendor', 'undisposable/undisposable');
  1035. $this->UndisposableEmail = new UndisposableEmail();
  1036. }
  1037. if (!$onlineMode) {
  1038. # crashed with white screen of death otherwise... (if foreign page is 404)
  1039. $this->UndisposableEmail->useOnlineList(false);
  1040. }
  1041. if (!class_exists('Validation')) {
  1042. App::uses('Validation', 'Utility');
  1043. }
  1044. if (!Validation::email($email)) {
  1045. return false;
  1046. }
  1047. if ($this->UndisposableEmail->isUndisposableEmail($email) === false) {
  1048. # trigger log
  1049. $this->log('Disposable Email detected: ' . h($email).' (IP '.env('REMOTE_ADDR').')', 'undisposable');
  1050. if ($proceed === true) {
  1051. return true;
  1052. }
  1053. return false;
  1054. }
  1055. return true;
  1056. }
  1057. /**
  1058. * //TODO: move outside of MyModel? use more generic "blocked" plugin!
  1059. * is blocked email?
  1060. * 2009-12-22 ms
  1061. */
  1062. public function validateNotBlocked($params) {
  1063. foreach ($params as $key => $value) {
  1064. $email = $value;
  1065. }
  1066. if (!isset($this->BlockedEmail)) {
  1067. if (false && App::import('Model', 'Tools.BlockedEmail')) {
  1068. $this->BlockedEmail = ClassRegistry::init('Tools.BlockedEmail');
  1069. } elseif (App::import('Model', 'Tools.Blacklist')) {
  1070. $this->BlockedEmail = ClassRegistry::init('Tools.Blacklist');
  1071. } else {
  1072. trigger_error('Model Tools.Blacklist not available');
  1073. return true;
  1074. }
  1075. }
  1076. if ($this->BlockedEmail->isBlocked($email)) {
  1077. return false;
  1078. }
  1079. return true;
  1080. }
  1081. /**
  1082. * Overrides the Core invalidate function from the Model class
  1083. * with the addition to use internationalization (I18n and L10n)
  1084. * @param string $field Name of the table column
  1085. * @param mixed $value The message or value which should be returned
  1086. * @param bool $translate If translation should be done here
  1087. * 2010-01-22 ms
  1088. */
  1089. public function invalidate($field, $value = null, $translate = true) {
  1090. if (!is_array($this->validationErrors)) {
  1091. $this->validationErrors = array();
  1092. }
  1093. if (empty($value)) {
  1094. $value = true;
  1095. } else {
  1096. $value = (array)$value;
  1097. }
  1098. if (is_array($value)) {
  1099. $value[0] = $translate ? __($value[0]) : $value[0];
  1100. $args = array_slice($value, 1);
  1101. $value = vsprintf($value[0], $args);
  1102. }
  1103. $this->validationErrors[$field] = $value;
  1104. }
  1105. /** General Model Functions **/
  1106. /**
  1107. * CAREFUL: use LIMIT due to Starker Serverlastigkeit! or CACHE it!
  1108. *
  1109. * e.g.: 'ORDER BY ".$this->umlautsOrderFix('User.nic')." ASC'
  1110. *
  1111. * @param string variable (to be correctly ordered)
  1112. */
  1113. public function umlautsOrderFix($var) {
  1114. return "REPLACE( REPLACE( REPLACE( REPLACE( REPLACE( REPLACE( REPLACE(".$var.", 'Ä', 'Ae'), 'Ö', 'Oe'), 'Ü', 'Ue'), 'ä', 'ae'), 'ö', 'oe'), 'ü','ue'), 'ß', 'ss')";
  1115. }
  1116. /**
  1117. * set + guaranteeFields!
  1118. * extends the core set function (only using data!!!)
  1119. * 2010-03-11 ms
  1120. */
  1121. public function set($data, $data2 = null, $requiredFields = array(), $fieldList = array()) {
  1122. if (!empty($requiredFields)) {
  1123. $data = $this->guaranteeFields($requiredFields, $data);
  1124. }
  1125. if (!empty($fieldList)) {
  1126. $data = $this->whitelist($fieldList, $data);
  1127. }
  1128. return parent::set($data, $data2);
  1129. }
  1130. /**
  1131. * 2011-06-01 ms
  1132. */
  1133. public function whitelist($fieldList, $data = null) {
  1134. $model = $this->alias;
  1135. foreach ($data[$model] as $key => $val) {
  1136. if (!in_array($key, $fieldList)) {
  1137. unset($data[$model][$key]);
  1138. }
  1139. }
  1140. return $data;
  1141. }
  1142. /**
  1143. * make sure required fields exists - in order to properly validate them
  1144. * @param array: field1, field2 - or field1, Model2.field1 etc
  1145. * @param array: data (optional, otherwise the array with the required fields will be returned)
  1146. * @return array
  1147. * 2010-03-11 ms
  1148. */
  1149. public function guaranteeFields($requiredFields, $data = null) {
  1150. $guaranteedFields = array();
  1151. foreach ($requiredFields as $column) {
  1152. if (strpos($column, '.') !== false) {
  1153. list($model, $column) = explode('.', $column, 2);
  1154. } else {
  1155. $model = $this->alias;
  1156. }
  1157. $guaranteedFields[$model][$column] = ''; # now field exists in any case!
  1158. }
  1159. if ($data === null) {
  1160. return $guaranteedFields;
  1161. }
  1162. if (!empty($guaranteedFields)) {
  1163. $data = Set::merge($guaranteedFields, $data);
  1164. }
  1165. return $data;
  1166. }
  1167. /**
  1168. * make certain fields a requirement for the form to validate
  1169. * (they must only be present - can still be empty, though!)
  1170. *
  1171. * @param array $fieldList
  1172. * @param bool $allowEmpty (or NULL to not touch already set elements)
  1173. * @return void
  1174. * 2012-02-20 ms
  1175. */
  1176. public function requireFields($requiredFields, $allowEmpty = null) {
  1177. if ($allowEmpty === null) {
  1178. $setAllowEmpty = true;
  1179. } else {
  1180. $setAllowEmpty = $allowEmpty;
  1181. }
  1182. foreach ($requiredFields as $column) {
  1183. if (strpos($column, '.') !== false) {
  1184. list($model, $column) = explode('.', $column, 2);
  1185. } else {
  1186. $model = $this->alias;
  1187. }
  1188. if ($model === $this->alias) {
  1189. if (empty($this->validate[$column])) {
  1190. $this->validate[$column]['notEmpty'] = array('rule'=>'notEmpty', 'required'=>true, 'allowEmpty' => $setAllowEmpty, 'message' => 'valErrMandatoryField');
  1191. } else {
  1192. $keys = array_keys($this->validate[$column]);
  1193. if (!in_array('rule', $keys)) {
  1194. $key = array_shift($keys);
  1195. $this->validate[$column][$key]['required'] = true;
  1196. if (!isset($this->validate[$column][$key]['allowEmpty'])) {
  1197. $this->validate[$column][$key]['allowEmpty'] = $setAllowEmpty;
  1198. }
  1199. } else {
  1200. $keys['required'] = true;
  1201. if (!isset($keys['allowEmpty'])) {
  1202. $keys['allowEmpty'] = $setAllowEmpty;
  1203. }
  1204. $this->validate[$column] = $keys;
  1205. }
  1206. }
  1207. }
  1208. }
  1209. }
  1210. /**
  1211. * instead of whitelisting
  1212. * @param array $blackList
  1213. * - array: fields to blacklist
  1214. * - boolean TRUE: removes all foreign_keys (_id and _key)
  1215. * note: one-dimensional
  1216. * 2009-06-19 ms
  1217. */
  1218. public function blacklist($blackList = array()) {
  1219. if ($blackList === true) {
  1220. //TODO
  1221. }
  1222. return array_diff(array_keys($this->schema()), (array )$blackList);
  1223. }
  1224. /**
  1225. * find a specific entry
  1226. * @param id
  1227. * @param fields
  1228. * @param contain
  1229. * 2009-11-14 ms
  1230. */
  1231. public function get($id, $fields = array(), $contain = array()) {
  1232. if (is_array($id)) {
  1233. $column = $id[0];
  1234. $value = $id[1];
  1235. } else {
  1236. $column = 'id';
  1237. $value = $id;
  1238. }
  1239. if ($fields == '*') {
  1240. $fields = $this->alias . '.*';
  1241. } elseif (empty($fields)) {
  1242. //$fields = array();
  1243. //$fields = $this->alias .'.*';
  1244. } else {
  1245. foreach ($fields as $row => $field) {
  1246. if (strpos($field, '.') !== false) {
  1247. continue;
  1248. }
  1249. $fields[$row] = $this->alias . '.' . $field;
  1250. }
  1251. }
  1252. $options = array(
  1253. 'conditions' => array($this->alias .'.'. $column => $value),
  1254. 'fields' => $fields,
  1255. );
  1256. if (!empty($contain)) {
  1257. $options['contain'] = $contain;
  1258. }
  1259. return $this->find('first', $options);
  1260. }
  1261. /**
  1262. * Update a row with certain fields (dont use "Model" as super-key)
  1263. * @param int $id
  1264. * @param array $data
  1265. * @return boolean
  1266. */
  1267. public function update($id, $data, $validate = false) {
  1268. $this->id = $id;
  1269. return $this->save($data, $validate, array_keys($data));
  1270. }
  1271. /**
  1272. * automagic increasing of a field with e.g.:
  1273. * $this->id = ID; $this->inc('weight',3);
  1274. * @deprecated use updateAll() instead!
  1275. * @param string fieldname
  1276. * @param int factor: defaults to 1 (could be negative as well - if field is signed and can be < 0)
  1277. */
  1278. public function inc($field, $factor = 1) {
  1279. $value = Set::extract($this->read($field), $this->alias . '.' . $field);
  1280. $value += $factor;
  1281. return $this->saveField($field, $value);
  1282. }
  1283. /**
  1284. * Toggles Field (Important etc)
  1285. * @param STRING fieldName
  1286. * @param INT id (cleaned!)
  1287. * @return ARRAY record: [Model][values],...
  1288. * AJAX?
  1289. * 2008-11-06 ms
  1290. */
  1291. public function toggleField($fieldName, $id) {
  1292. $record = $this->get($id, array('id', $fieldName));
  1293. if (!empty($record) && !empty($fieldName) && $this->hasField($fieldName)) {
  1294. $record[$this->alias][$fieldName] = ($record[$this->alias][$fieldName] == 1 ? 0 : 1);
  1295. $this->id = $id;
  1296. $this->saveField($fieldName, $record[$this->alias][$fieldName]);
  1297. }
  1298. return $record;
  1299. }
  1300. /**
  1301. * truncate TABLE (already validated, that table exists)
  1302. * @param string table [default:FALSE = current model table]
  1303. */
  1304. public function truncate($table = null) {
  1305. if (empty($table)) {
  1306. $table = $this->table;
  1307. }
  1308. $db = ConnectionManager::getDataSource($this->useDbConfig);
  1309. $res = $db->truncate($table);
  1310. return $res;
  1311. }
  1312. /** Deep Lists **/
  1313. /**
  1314. * recursive Dropdown Lists
  1315. * NEEDS tree behavior, NEEDS lft, rght, parent_id (!)
  1316. * //FIXME
  1317. * 2008-01-02 ms
  1318. */
  1319. public function recursiveSelect($conditions = array(), $attachTree = false, $spacer = '-- ') {
  1320. if ($attachTree) {
  1321. $this->Behaviors->attach('Tree');
  1322. }
  1323. $data = $this->generateTreeList($conditions, null, null, $spacer);
  1324. return $data;
  1325. }
  1326. /**
  1327. * from http://othy.wordpress.com/2006/06/03/generatenestedlist/
  1328. * NEEDS parent_id
  1329. * //TODO refactor for 1.2
  1330. * 2009-08-12 ms
  1331. */
  1332. public function generateNestedList($conditions = null, $indent = '- - ') {
  1333. $cats = $this->find('threaded', array('conditions'=>$conditions, 'fields'=>array($this->alias.'.id', $this->alias.'.'.$this->displayField, $this->alias.'.parent_id')));
  1334. $glist = $this->_generateNestedList($cats, $indent);
  1335. return $glist;
  1336. }
  1337. /**
  1338. * from http://othy.wordpress.com/2006/06/03/generatenestedlist/
  1339. * @protected
  1340. * 2009-08-12 ms
  1341. */
  1342. public function _generateNestedList($cats, $indent, $level = 0) {
  1343. static $list = array();
  1344. for ($i = 0, $c = count($cats); $i < $c; $i++) {
  1345. $list[$cats[$i][$this->alias]['id']] = str_repeat($indent, $level) . $cats[$i][$this->alias][$this->displayField];
  1346. if (isset($cats[$i]['children']) && !empty($cats[$i]['children'])) {
  1347. $this->_generateNestedList($cats[$i]['children'], $indent, $level + 1);
  1348. }
  1349. }
  1350. return $list;
  1351. }
  1352. }