MyModel.php 48 KB

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