my_model.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. <?php
  2. class MyModel extends Model {
  3. var $recursive = -1;
  4. var $actsAs = array('Containable');
  5. /**
  6. * @return string Error message with error number
  7. * 2010-11-06 ms
  8. */
  9. public function lastError() {
  10. $db = $this->getDataSource();
  11. return $db->lastError();
  12. }
  13. /** Validation Functions **/
  14. /**
  15. * validates a primary or foreign key depending on the current schema data for this field
  16. * recognizes uuid (char36) and aiid (int10 unsigned) - not yet mixed (varchar36)
  17. * more useful than using numeric or notEmpty which are type specific
  18. * @param array $data
  19. * @param array $options
  20. * - allowEmpty
  21. * 2011-06-21 ms
  22. */
  23. function validateKey($data = array(), $options = array()) {
  24. $key = array_shift(array_keys($data));
  25. $value = array_shift($data);
  26. $schema = $this->schema($key);
  27. if (!$schema) {
  28. return true;
  29. }
  30. $defaults = array(
  31. 'allowEmpty' => false,
  32. );
  33. $options = am($defaults, $options);
  34. if ($schema['type'] != 'integer') {
  35. if ($options['allowEmpty'] && $value === '') {
  36. return true;
  37. }
  38. return Validation::uuid($value);
  39. }
  40. if ($options['allowEmpty'] && $value === 0) {
  41. return true;
  42. }
  43. return is_numeric($value) && (int)$value == $value && $value > 0;
  44. }
  45. /**
  46. * checks if the passed enum value is valid
  47. * 2010-02-09 ms
  48. */
  49. function validateEnum($field = array(), $enum = null, $additionalKeys = array()) {
  50. $valueKey = array_shift(array_keys($field)); # auto-retrieve
  51. $value = $field[$valueKey];
  52. $keys = array();
  53. if ($enum === true) {
  54. $enum = $valueKey;
  55. }
  56. if ($enum !== null) {
  57. if (!method_exists($this, $enum)) {
  58. trigger_error('Enum method \'' . $enum . '()\' not exists', E_USER_ERROR);
  59. return false;
  60. }
  61. //TODO: make static
  62. $keys = $this->{$enum}();
  63. }
  64. $keys = array_merge($additionalKeys, array_keys($keys));
  65. if (!empty($keys) && in_array($value, $keys)) {
  66. return true;
  67. }
  68. return false;
  69. }
  70. /**
  71. * checks if the content of 2 fields are equal
  72. * Does not check on empty fields! Return TRUE even if both are empty (secure against empty in another rule)!
  73. * 2009-01-22 ms
  74. */
  75. function validateIdentical($data = array(), $compareWith = null, $options = array()) {
  76. if (is_array($data)) {
  77. $value = array_shift($data);
  78. } else {
  79. $value = $data;
  80. }
  81. $compareValue = $this->data[$this->alias][$compareWith];
  82. $matching = array('string' => 'string', 'int' => 'integer', 'float' => 'float', 'bool' => 'boolean');
  83. if (!empty($options['cast']) && array_key_exists($options['cast'], $matching)) {
  84. # cast values to string/int/float/bool if desired
  85. settype($compareValue, $matching[$options['cast']]);
  86. settype($value, $matching[$options['cast']]);
  87. }
  88. return ($compareValue === $value);
  89. }
  90. /**
  91. * checks a record, if it is unique - depending on other fields in this table (transfered as array)
  92. * example in model: 'rule' => array ('validateUnique',array('belongs_to_table_id','some_id','user_id')),
  93. * if all keys (of the array transferred) match a record, return false, otherwise true
  94. * @param ARRAY other fields
  95. * TODO: add possibity of deep nested validation (User -> Comment -> CommentCategory: UNIQUE comment_id, Comment.user_id)
  96. * 2010-01-30 ms
  97. */
  98. function validateUnique($data, $fields = array(), $options = array()) {
  99. $id = (!empty($this->data[$this->alias]['id']) ? $this->data[$this->alias]['id'] : 0);
  100. foreach ($data as $key => $value) {
  101. $fieldName = $key;
  102. $fieldValue = $value; // equals: $this->data[$this->alias][$fieldName]
  103. }
  104. if (empty($fieldName) || empty($fieldValue)) { // return true, if nothing is transfered (check on that first)
  105. return true;
  106. }
  107. $conditions = array($this->alias . '.' . $fieldName => $fieldValue, // Model.field => $this->data['Model']['field']
  108. $this->alias . '.id !=' => $id, );
  109. # careful, if fields is not manually filled, the options will be the second param!!! big problem...
  110. foreach ((array )$fields as $dependingField) {
  111. if (isset($this->data[$this->alias][$dependingField])) { // add ONLY if some content is transfered (check on that first!)
  112. $conditions[$this->alias . '.' . $dependingField] = $this->data[$this->alias][$dependingField];
  113. } elseif (!empty($this->data['Validation'][$dependingField])) { // add ONLY if some content is transfered (check on that first!
  114. $conditions[$this->alias . '.' . $dependingField] = $this->data['Validation'][$dependingField];
  115. } elseif (!empty($id)) {
  116. # manual query! (only possible on edit)
  117. $res = $this->find('first', array('fields' => array($this->alias.'.'.$dependingField), 'conditions' => array($this->alias.'.id' => $this->data[$this->alias]['id'])));
  118. if (!empty($res)) {
  119. $conditions[$this->alias . '.' . $dependingField] = $res[$this->alias][$dependingField];
  120. }
  121. }
  122. }
  123. $this->recursive = -1;
  124. if (count($conditions) > 2) {
  125. $this->recursive = 0;
  126. }
  127. $res = $this->find('first', array('fields' => array($this->alias . '.id'), 'conditions' => $conditions));
  128. if (!empty($res)) {
  129. return false;
  130. }
  131. return true;
  132. }
  133. /**
  134. * @param array $data
  135. * @param array $options
  136. * - scope (array of other fields as scope - isUnique dependent on other fields of the table)
  137. * - batch (defaults to true, remembers previous values in order to validate batch imports)
  138. * example in model: 'rule' => array ('validateUniqueExt', array('scope'=>array('belongs_to_table_id','some_id','user_id'))),
  139. * http://groups.google.com/group/cake-php/browse_thread/thread/880ee963456739ec
  140. * //TODO: test!!!
  141. * 2011-03-27 ms
  142. */
  143. function validateUniqueExt($data, $options = array()) {
  144. foreach ($data as $key => $value) {
  145. $fieldName = $key;
  146. $fieldValue = $value;
  147. }
  148. $defaults = array('batch' => true, 'scope' => array());
  149. $options = array_merge($defaults, $options);
  150. # for batch
  151. if ($options['batch'] !== false && !empty($this->batchRecords)) {
  152. if (array_key_exists($value, $this->batchRecords[$fieldName])) {
  153. return $options['scope'] === $this->batchRecords[$fieldName][$value];
  154. }
  155. }
  156. # continue with validation
  157. if (!$this->validateUnique($data, $options['scope'])) {
  158. return false;
  159. }
  160. # for batch
  161. if ($options['batch'] !== false) {
  162. if (!isset($this->batchRecords)) {
  163. $this->batchRecords = array();
  164. }
  165. $this->batchRecords[$fieldName][$value] = $scope;
  166. }
  167. return true;
  168. }
  169. /**
  170. * checks if a url is valid AND accessable (returns false otherwise)
  171. * @param array/string $data: full url(!) starting with http://...
  172. * @options
  173. * - allowEmpty TRUE/FALSE (TRUE: if empty => return TRUE)
  174. * - required TRUE/FALSE (TRUE: overrides allowEmpty)
  175. * - autoComplete (default: TRUE)
  176. * - deep (default: TRUE)
  177. * 2010-10-18 ms
  178. */
  179. function validateUrl($data, $options = array()) {
  180. //$arguments = func_get_args();
  181. if (is_array($data)) {
  182. $url = array_shift($data);
  183. } else {
  184. $url = $data;
  185. }
  186. if (empty($url)) {
  187. if (!empty($options['allowEmpty']) && empty($options['required'])) {
  188. return true;
  189. }
  190. return false;
  191. }
  192. if (!isset($options['autoComplete']) || $options['autoComplete'] !== false) {
  193. $url = $this->_autoCompleteUrl($url);
  194. }
  195. if (!isset($options['strict']) || $options['strict'] !== false) {
  196. $options['strict'] = true;
  197. }
  198. # validation
  199. if (!Validation::url($url, $options['strict'])) {
  200. return false;
  201. }
  202. # same domain?
  203. if (!empty($options['sameDomain']) && !empty($_SERVER['HTTP_HOST'])) {
  204. $is = parse_url($url, PHP_URL_HOST);
  205. $expected = $_SERVER['HTTP_HOST'];
  206. if (mb_strtolower($is) !== mb_strtolower($expected)) {
  207. return false;
  208. }
  209. }
  210. if (isset($options['deep']) && $options['deep'] === false) {
  211. return true;
  212. }
  213. return $this->_validUrl($url);
  214. }
  215. function _autoCompleteUrl($url) {
  216. if (mb_strpos($url, '://') === false && mb_strpos($url, 'www.') === 0) {
  217. $url = 'http://' . $url;
  218. } elseif (mb_strpos($url, '/') === 0) {
  219. $url = Router::url($url, true);
  220. }
  221. return $url;
  222. }
  223. /**
  224. * checks if a url is valid
  225. * @param string url
  226. * 2009-02-27 ms
  227. */
  228. function _validUrl($url = null) {
  229. App::import('Component', 'Tools.Common');
  230. $headers = CommonComponent::getHeaderFromUrl($url);
  231. if ($headers !== false) {
  232. $headers = implode("\n", $headers);
  233. return ((bool)preg_match('#^HTTP/.*\s+[(200|301|302)]+\s#i', $headers) && !(bool)preg_match('#^HTTP/.*\s+[(404|999)]+\s#i', $headers));
  234. }
  235. return false;
  236. }
  237. /**
  238. * Validation of DateTime Fields (both Date and Time together)
  239. * @param options
  240. * - dateFormat (defaults to 'ymd')
  241. * - allowEmpty
  242. * - after/before (fieldName to validate against)
  243. * - min/max (defaults to >= 1 - at least 1 minute apart)
  244. * 2011-03-02 ms
  245. */
  246. function validateDateTime($data, $options = array()) {
  247. $format = !empty($options['dateFormat']) ? $options['dateFormat'] : 'ymd';
  248. if (is_array($data)) {
  249. $value = array_shift($data);
  250. } else {
  251. $value = $data;
  252. }
  253. $dateTime = explode(' ', trim($value), 2);
  254. $date = $dateTime[0];
  255. $time = (!empty($dateTime[1]) ? $dateTime[1] : '');
  256. if (!empty($options['allowEmpty']) && (empty($date) && empty($time) || $date == DEFAULT_DATE && $time == DEFAULT_TIME || $date == DEFAULT_DATE && empty($time))) {
  257. return true;
  258. }
  259. /*
  260. if ($this->validateDate($date, $options) && $this->validateTime($time, $options)) {
  261. return true;
  262. }
  263. */
  264. if (Validation::date($date, $format) && Validation::time($time)) {
  265. # after/before?
  266. $minutes = isset($options['min']) ? $options['min'] : 1;
  267. if (!empty($options['after']) && isset($this->data[$this->alias][$options['after']])) {
  268. if (strtotime($this->data[$this->alias][$options['after']]) > strtotime($value) - $minutes) {
  269. return false;
  270. }
  271. }
  272. if (!empty($options['before']) && isset($this->data[$this->alias][$options['before']])) {
  273. if (strtotime($this->data[$this->alias][$options['before']]) < strtotime($value) + $minutes) {
  274. return false;
  275. }
  276. }
  277. return true;
  278. }
  279. return false;
  280. }
  281. /**
  282. * Validation of Date Fields (as the core one is buggy!!!)
  283. * @param options
  284. * - dateFormat (defaults to 'ymd')
  285. * - allowEmpty
  286. * - after/before (fieldName to validate against)
  287. * - min (defaults to 0 - equal is OK too)
  288. * 2011-03-02 ms
  289. */
  290. function validateDate($data, $options = array()) {
  291. $format = !empty($options['format']) ? $options['format'] : 'ymd';
  292. if (is_array($data)) {
  293. $value = array_shift($data);
  294. } else {
  295. $value = $data;
  296. }
  297. $dateTime = explode(' ', trim($value), 2);
  298. $date = $dateTime[0];
  299. if (!empty($options['allowEmpty']) && (empty($date) || $date == DEFAULT_DATE)) {
  300. return true;
  301. }
  302. if (Validation::date($date, $format)) {
  303. # after/before?
  304. $days = !empty($options['min']) ? $options['min'] : 0;
  305. if (!empty($options['after']) && isset($this->data[$this->alias][$options['after']])) {
  306. if ($this->data[$this->alias][$options['after']] > date(FORMAT_DB_DATE, strtotime($date) - $days * DAY)) {
  307. return false;
  308. }
  309. }
  310. if (!empty($options['before']) && isset($this->data[$this->alias][$options['before']])) {
  311. if ($this->data[$this->alias][$options['before']] < date(FORMAT_DB_DATE, strtotime($date) + $days * DAY)) {
  312. return false;
  313. }
  314. }
  315. return true;
  316. }
  317. return false;
  318. }
  319. /**
  320. * @param options
  321. * - timeFormat (defaults to 'hms')
  322. * - allowEmpty
  323. * - after/before (fieldName to validate against)
  324. * - min/max (defaults to >= 1 - at least 1 minute apart)
  325. * 2011-03-02 ms
  326. */
  327. function validateTime($data, $options = array()) {
  328. if (is_array($data)) {
  329. $value = array_shift($data);
  330. } else {
  331. $value = $data;
  332. }
  333. $dateTime = explode(' ', trim($value), 2);
  334. $value = array_pop($dateTime);
  335. if (Validation::time($value)) {
  336. # after/before?
  337. if (!empty($options['after']) && isset($this->data[$this->alias][$options['after']])) {
  338. if ($this->data[$this->alias][$options['after']] >= $value) {
  339. return false;
  340. }
  341. }
  342. if (!empty($options['before']) && isset($this->data[$this->alias][$options['before']])) {
  343. if ($this->data[$this->alias][$options['before']] <= $value) {
  344. return false;
  345. }
  346. }
  347. return true;
  348. }
  349. return false;
  350. }
  351. //TODO
  352. /**
  353. * Validation of Date Fields (>= minDate && <= maxDate)
  354. * @param options
  355. * - min/max (TODO!!)
  356. * 2010-01-20 ms
  357. */
  358. function validateDateRange($data, $options = array()) {
  359. }
  360. //TODO
  361. /**
  362. * Validation of Time Fields (>= minTime && <= maxTime)
  363. * @param options
  364. * - min/max (TODO!!)
  365. * 2010-01-20 ms
  366. */
  367. function validateTimeRange($data, $options = array()) {
  368. }
  369. /**
  370. * model validation rule for email addresses
  371. * 2010-01-14 ms
  372. */
  373. function validateUndisposable($data, $proceed = false) {
  374. $email = array_shift($data);
  375. if (empty($email)) {
  376. return true;
  377. }
  378. return $this->isUndisposableEmail($email, false, $proceed);
  379. }
  380. /**
  381. * NOW: can be set to work offline only (if server is down etc)
  382. *
  383. * checks if a email is not from a garbige hoster
  384. * @param string email (neccessary)
  385. * @return boolean true if valid, else false
  386. * 2009-03-09 ms
  387. */
  388. function isUndisposableEmail($email, $onlineMode = false, $proceed = false) {
  389. if (!isset($this->UndisposableEmail)) {
  390. App::import('Vendor', 'undisposable');
  391. $this->UndisposableEmail = new UndisposableEmail();
  392. }
  393. if (!$onlineMode) {
  394. # crashed with white screen of death otherwise... (if foreign page is 404)
  395. $this->UndisposableEmail->useOnlineList(false);
  396. }
  397. if (!class_exists('Validation')) {
  398. App::import('Core', 'Validation');
  399. }
  400. if (!Validation::email($email)) {
  401. return false;
  402. }
  403. if ($this->UndisposableEmail->isUndisposableEmail($email) === false) {
  404. # trigger log
  405. $this->log('Disposable Email detected: ' . h($email).' (IP '.env('REMOTE_ADDR').')', 'undisposable');
  406. if ($proceed === true) {
  407. return true;
  408. }
  409. return false;
  410. }
  411. return true;
  412. }
  413. /**
  414. * //TODO: move outside of MyModel? use more generic "blocked" plugin!
  415. * is blocked email?
  416. * 2009-12-22 ms
  417. */
  418. function validateNotBlocked($params) {
  419. foreach ($params as $key => $value) {
  420. $email = $value;
  421. }
  422. if (!isset($this->BlockedEmail)) {
  423. if (!App::import('Model', 'Tools.BlockedEmail')) {
  424. trigger_error('Model Tools.BlockedEmail not available');
  425. return true;
  426. }
  427. $this->BlockedEmail = ClassRegistry::init('Tools.BlockedEmail');
  428. }
  429. if ($this->BlockedEmail->isBlocked($email)) {
  430. return false;
  431. }
  432. return true;
  433. }
  434. /**
  435. * Overrides the Core invalidate function from the Model class
  436. * with the addition to use internationalization (I18n and L10n)
  437. * @param string $field Name of the table column
  438. * @param mixed $value The message or value which should be returned
  439. * @param bool $translate If translation should be done here
  440. * 2010-01-22 ms
  441. */
  442. function invalidate($field, $value = null, $translate = true) {
  443. if (!is_array($this->validationErrors)) {
  444. $this->validationErrors = array();
  445. }
  446. if (empty($value)) {
  447. $value = true;
  448. } else {
  449. $value = (array)$value;
  450. }
  451. if (is_array($value)) {
  452. $value[0] = $translate ? __($value[0], true) : $value[0];
  453. $args = array_slice($value, 1);
  454. $value = vsprintf($value[0], $args);
  455. }
  456. $this->validationErrors[$field] = $value;
  457. }
  458. /** DEPRECATED STUFF - will be removed in stage 2 **/
  459. /**
  460. * @param string $value or array $keys or NULL for complete array result
  461. * @return string/array
  462. * static enums
  463. * @deprecated
  464. * 2009-11-05 ms
  465. */
  466. public static function enum($value, $options, $default = '') {
  467. if ($value !== null && !is_array($value)) {
  468. if (array_key_exists($value, $options)) {
  469. return $options[$value];
  470. }
  471. return $default;
  472. } elseif ($value !== null) {
  473. $newOptions = array();
  474. foreach ($value as $v) {
  475. $newOptions[$v] = $options[$v];
  476. }
  477. return $newOptions;
  478. }
  479. return $options;
  480. }
  481. }