451 lines
15KB

  1. /**
  2. * Yii validation module.
  3. *
  4. * This JavaScript module provides the validation methods for the built-in validators.
  5. *
  6. * @link http://www.yiiframework.com/
  7. * @copyright Copyright (c) 2008 Yii Software LLC
  8. * @license http://www.yiiframework.com/license/
  9. * @author Qiang Xue <qiang.xue@gmail.com>
  10. * @since 2.0
  11. */
  12. yii.validation = (function ($) {
  13. var pub = {
  14. isEmpty: function (value) {
  15. return value === null || value === undefined || value == [] || value === '';
  16. },
  17. addMessage: function (messages, message, value) {
  18. messages.push(message.replace(/\{value\}/g, value));
  19. },
  20. required: function (value, messages, options) {
  21. var valid = false;
  22. if (options.requiredValue === undefined) {
  23. var isString = typeof value == 'string' || value instanceof String;
  24. if (options.strict && value !== undefined || !options.strict && !pub.isEmpty(isString ? $.trim(value) : value)) {
  25. valid = true;
  26. }
  27. } else if (!options.strict && value == options.requiredValue || options.strict && value === options.requiredValue) {
  28. valid = true;
  29. }
  30. if (!valid) {
  31. pub.addMessage(messages, options.message, value);
  32. }
  33. },
  34. 'boolean': function (value, messages, options) {
  35. if (options.skipOnEmpty && pub.isEmpty(value)) {
  36. return;
  37. }
  38. var valid = !options.strict && (value == options.trueValue || value == options.falseValue)
  39. || options.strict && (value === options.trueValue || value === options.falseValue);
  40. if (!valid) {
  41. pub.addMessage(messages, options.message, value);
  42. }
  43. },
  44. string: function (value, messages, options) {
  45. if (options.skipOnEmpty && pub.isEmpty(value)) {
  46. return;
  47. }
  48. if (typeof value !== 'string') {
  49. pub.addMessage(messages, options.message, value);
  50. return;
  51. }
  52. if (options.min !== undefined && value.length < options.min) {
  53. pub.addMessage(messages, options.tooShort, value);
  54. }
  55. if (options.max !== undefined && value.length > options.max) {
  56. pub.addMessage(messages, options.tooLong, value);
  57. }
  58. if (options.is !== undefined && value.length != options.is) {
  59. pub.addMessage(messages, options.notEqual, value);
  60. }
  61. },
  62. file: function (attribute, messages, options) {
  63. var files = getUploadedFiles(attribute, messages, options);
  64. $.each(files, function (i, file) {
  65. validateFile(file, messages, options);
  66. });
  67. },
  68. image: function (attribute, messages, options, deferred) {
  69. var files = getUploadedFiles(attribute, messages, options);
  70. $.each(files, function (i, file) {
  71. validateFile(file, messages, options);
  72. // Skip image validation if FileReader API is not available
  73. if (typeof FileReader === "undefined") {
  74. return;
  75. }
  76. var def = $.Deferred(),
  77. fr = new FileReader(),
  78. img = new Image();
  79. img.onload = function () {
  80. if (options.minWidth && this.width < options.minWidth) {
  81. messages.push(options.underWidth.replace(/\{file\}/g, file.name));
  82. }
  83. if (options.maxWidth && this.width > options.maxWidth) {
  84. messages.push(options.overWidth.replace(/\{file\}/g, file.name));
  85. }
  86. if (options.minHeight && this.height < options.minHeight) {
  87. messages.push(options.underHeight.replace(/\{file\}/g, file.name));
  88. }
  89. if (options.maxHeight && this.height > options.maxHeight) {
  90. messages.push(options.overHeight.replace(/\{file\}/g, file.name));
  91. }
  92. def.resolve();
  93. };
  94. img.onerror = function () {
  95. messages.push(options.notImage.replace(/\{file\}/g, file.name));
  96. def.resolve();
  97. };
  98. fr.onload = function () {
  99. img.src = fr.result;
  100. };
  101. // Resolve deferred if there was error while reading data
  102. fr.onerror = function () {
  103. def.resolve();
  104. };
  105. fr.readAsDataURL(file);
  106. deferred.push(def);
  107. });
  108. },
  109. number: function (value, messages, options) {
  110. if (options.skipOnEmpty && pub.isEmpty(value)) {
  111. return;
  112. }
  113. if (typeof value === 'string' && !value.match(options.pattern)) {
  114. pub.addMessage(messages, options.message, value);
  115. return;
  116. }
  117. if (options.min !== undefined && value < options.min) {
  118. pub.addMessage(messages, options.tooSmall, value);
  119. }
  120. if (options.max !== undefined && value > options.max) {
  121. pub.addMessage(messages, options.tooBig, value);
  122. }
  123. },
  124. range: function (value, messages, options) {
  125. if (options.skipOnEmpty && pub.isEmpty(value)) {
  126. return;
  127. }
  128. if (!options.allowArray && $.isArray(value)) {
  129. pub.addMessage(messages, options.message, value);
  130. return;
  131. }
  132. var inArray = true;
  133. $.each($.isArray(value) ? value : [value], function (i, v) {
  134. if ($.inArray(v, options.range) == -1) {
  135. inArray = false;
  136. return false;
  137. } else {
  138. return true;
  139. }
  140. });
  141. if (options.not === inArray) {
  142. pub.addMessage(messages, options.message, value);
  143. }
  144. },
  145. regularExpression: function (value, messages, options) {
  146. if (options.skipOnEmpty && pub.isEmpty(value)) {
  147. return;
  148. }
  149. if (!options.not && !value.match(options.pattern) || options.not && value.match(options.pattern)) {
  150. pub.addMessage(messages, options.message, value);
  151. }
  152. },
  153. email: function (value, messages, options) {
  154. if (options.skipOnEmpty && pub.isEmpty(value)) {
  155. return;
  156. }
  157. var valid = true;
  158. var regexp = /^((?:"?([^"]*)"?\s)?)(?:\s+)?(?:(<?)((.+)@([^>]+))(>?))$/,
  159. matches = regexp.exec(value);
  160. if (matches === null) {
  161. valid = false
  162. } else {
  163. if (options.enableIDN) {
  164. matches[5] = punycode.toASCII(matches[5]);
  165. matches[6] = punycode.toASCII(matches[6]);
  166. value = matches[1] + matches[3] + matches[5] + '@' + matches[6] + matches[7];
  167. }
  168. if (matches[5].length > 64) {
  169. valid = false;
  170. } else if ((matches[5] + '@' + matches[6]).length > 254) {
  171. valid = false;
  172. } else {
  173. valid = value.match(options.pattern) || (options.allowName && value.match(options.fullPattern));
  174. }
  175. }
  176. if (!valid) {
  177. pub.addMessage(messages, options.message, value);
  178. }
  179. },
  180. url: function (value, messages, options) {
  181. if (options.skipOnEmpty && pub.isEmpty(value)) {
  182. return;
  183. }
  184. if (options.defaultScheme && !value.match(/:\/\//)) {
  185. value = options.defaultScheme + '://' + value;
  186. }
  187. var valid = true;
  188. if (options.enableIDN) {
  189. var regexp = /^([^:]+):\/\/([^\/]+)(.*)$/,
  190. matches = regexp.exec(value);
  191. if (matches === null) {
  192. valid = false;
  193. } else {
  194. value = matches[1] + '://' + punycode.toASCII(matches[2]) + matches[3];
  195. }
  196. }
  197. if (!valid || !value.match(options.pattern)) {
  198. pub.addMessage(messages, options.message, value);
  199. }
  200. },
  201. trim: function ($form, attribute, options) {
  202. var $input = $form.find(attribute.input);
  203. var value = $input.val();
  204. if (!options.skipOnEmpty || !pub.isEmpty(value)) {
  205. value = $.trim(value);
  206. $input.val(value);
  207. }
  208. return value;
  209. },
  210. captcha: function (value, messages, options) {
  211. if (options.skipOnEmpty && pub.isEmpty(value)) {
  212. return;
  213. }
  214. // CAPTCHA may be updated via AJAX and the updated hash is stored in body data
  215. var hash = $('body').data(options.hashKey);
  216. if (hash == null) {
  217. hash = options.hash;
  218. } else {
  219. hash = hash[options.caseSensitive ? 0 : 1];
  220. }
  221. var v = options.caseSensitive ? value : value.toLowerCase();
  222. for (var i = v.length - 1, h = 0; i >= 0; --i) {
  223. h += v.charCodeAt(i);
  224. }
  225. if (h != hash) {
  226. pub.addMessage(messages, options.message, value);
  227. }
  228. },
  229. compare: function (value, messages, options) {
  230. if (options.skipOnEmpty && pub.isEmpty(value)) {
  231. return;
  232. }
  233. var compareValue, valid = true;
  234. if (options.compareAttribute === undefined) {
  235. compareValue = options.compareValue;
  236. } else {
  237. compareValue = $('#' + options.compareAttribute).val();
  238. }
  239. if (options.type === 'number') {
  240. value = parseFloat(value);
  241. compareValue = parseFloat(compareValue);
  242. }
  243. switch (options.operator) {
  244. case '==':
  245. valid = value == compareValue;
  246. break;
  247. case '===':
  248. valid = value === compareValue;
  249. break;
  250. case '!=':
  251. valid = value != compareValue;
  252. break;
  253. case '!==':
  254. valid = value !== compareValue;
  255. break;
  256. case '>':
  257. valid = value > compareValue;
  258. break;
  259. case '>=':
  260. valid = value >= compareValue;
  261. break;
  262. case '<':
  263. valid = value < compareValue;
  264. break;
  265. case '<=':
  266. valid = value <= compareValue;
  267. break;
  268. default:
  269. valid = false;
  270. break;
  271. }
  272. if (!valid) {
  273. pub.addMessage(messages, options.message, value);
  274. }
  275. },
  276. ip: function (value, messages, options) {
  277. var getIpVersion = function (value) {
  278. return value.indexOf(':') === -1 ? 4 : 6;
  279. };
  280. var negation = null, cidr = null;
  281. if (options.skipOnEmpty && pub.isEmpty(value)) {
  282. return;
  283. }
  284. var matches = new RegExp(options.ipParsePattern).exec(value);
  285. if (matches) {
  286. negation = matches[1] || null;
  287. value = matches[2];
  288. cidr = matches[4] || null;
  289. }
  290. if (options.subnet === true && cidr === null) {
  291. pub.addMessage(messages, options.messages.noSubnet, value);
  292. return;
  293. }
  294. if (options.subnet === false && cidr !== null) {
  295. pub.addMessage(messages, options.messages.hasSubnet, value);
  296. return;
  297. }
  298. if (options.negation === false && negation !== null) {
  299. pub.addMessage(messages, options.messages.message, value);
  300. return;
  301. }
  302. if (getIpVersion(value) == 6) {
  303. if (!options.ipv6) {
  304. pub.addMessage(messages, options.messages.ipv6NotAllowed, value);
  305. }
  306. if (!(new RegExp(options.ipv6Pattern)).test(value)) {
  307. pub.addMessage(messages, options.messages.message, value);
  308. }
  309. } else {
  310. if (!options.ipv4) {
  311. pub.addMessage(messages, options.messages.ipv4NotAllowed, value);
  312. }
  313. if (!(new RegExp(options.ipv4Pattern)).test(value)) {
  314. pub.addMessage(messages, options.messages.message, value);
  315. }
  316. }
  317. }
  318. };
  319. function getUploadedFiles(attribute, messages, options) {
  320. // Skip validation if File API is not available
  321. if (typeof File === "undefined") {
  322. return [];
  323. }
  324. var files = $(attribute.input, attribute.$form).get(0).files;
  325. if (!files) {
  326. messages.push(options.message);
  327. return [];
  328. }
  329. if (files.length === 0) {
  330. if (!options.skipOnEmpty) {
  331. messages.push(options.uploadRequired);
  332. }
  333. return [];
  334. }
  335. if (options.maxFiles && options.maxFiles < files.length) {
  336. messages.push(options.tooMany);
  337. return [];
  338. }
  339. return files;
  340. }
  341. function validateFile(file, messages, options) {
  342. if (options.extensions && options.extensions.length > 0) {
  343. var index, ext;
  344. index = file.name.lastIndexOf('.');
  345. if (!~index) {
  346. ext = '';
  347. } else {
  348. ext = file.name.substr(index + 1, file.name.length).toLowerCase();
  349. }
  350. if (!~options.extensions.indexOf(ext)) {
  351. messages.push(options.wrongExtension.replace(/\{file\}/g, file.name));
  352. }
  353. }
  354. if (options.mimeTypes && options.mimeTypes.length > 0) {
  355. if (!validateMimeType(options.mimeTypes, file.type)) {
  356. messages.push(options.wrongMimeType.replace(/\{file\}/g, file.name));
  357. }
  358. }
  359. if (options.maxSize && options.maxSize < file.size) {
  360. messages.push(options.tooBig.replace(/\{file\}/g, file.name));
  361. }
  362. if (options.minSize && options.minSize > file.size) {
  363. messages.push(options.tooSmall.replace(/\{file\}/g, file.name));
  364. }
  365. }
  366. function validateMimeType(mimeTypes, fileType) {
  367. for (var i = 0, len = mimeTypes.length; i < len; i++) {
  368. if (new RegExp(mimeTypes[i]).test(fileType)) {
  369. return true;
  370. }
  371. }
  372. return false;
  373. }
  374. return pub;
  375. })(jQuery);