MyModel.php 46 KB

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