BCGgs1128.barcode.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  1. <?php
  2. /**
  3. *--------------------------------------------------------------------
  4. *
  5. * Calculate the GS1-128 based on the Code-128 encoding.
  6. *
  7. *--------------------------------------------------------------------
  8. * Copyright (C) Jean-Sebastien Goupil
  9. * http://www.barcodephp.com
  10. */
  11. include_once('BCGParseException.php');
  12. include_once('BCGcode128.barcode.php');
  13. class BCGgs1128 extends BCGcode128 {
  14. const KIND_OF_DATA = 0;
  15. const MINLENGTH = 1;
  16. const MAXLENGTH = 2;
  17. const CHECKSUM = 3;
  18. const NUMERIC = 0;
  19. const ALPHA_NUMERIC = 1;
  20. const DATE_YYMMDD = 2;
  21. const ID = 0;
  22. const CONTENT = 1;
  23. const MAX_ID_FORMATED = 6;
  24. const MAX_ID_NOT_FORMATED = 4;
  25. const MAX_GS1128_CHARS = 48;
  26. private $strictMode;
  27. private $allowsUnknownIdentifier;
  28. private $noLengthLimit;
  29. private $identifiersId = array();
  30. private $identifiersContent = array();
  31. private $identifiersAi = array();
  32. /**
  33. * Constructors
  34. *
  35. * @param char $start
  36. */
  37. public function __construct($start = null) {
  38. if ($start === null) {
  39. $start = 'C';
  40. }
  41. parent::__construct($start);
  42. /* Application Identifiers (AIs) */
  43. /*
  44. array ( KIND_OF_DATA , MINLENGTH , MAXLENGTH , CHECKSUM )
  45. KIND_OF_DATA: NUMERIC , ALPHA_NUMERIC or DATE_YYMMDD
  46. CHECKSUM: bool (true / false)
  47. */
  48. $this->identifiersAi = array(
  49. '00' => array(self::NUMERIC, 18, 18, true),
  50. '01' => array(self::NUMERIC, 14, 14, true),
  51. '02' => array(self::NUMERIC, 14, 14, true),
  52. '10' => array(self::ALPHA_NUMERIC, 1, 20, false),
  53. '11' => array(self::DATE_YYMMDD, 6, 6, false),
  54. '12' => array(self::DATE_YYMMDD, 6, 6, false),
  55. '13' => array(self::DATE_YYMMDD, 6, 6, false),
  56. '15' => array(self::DATE_YYMMDD, 6, 6, false),
  57. '17' => array(self::DATE_YYMMDD, 6, 6, false),
  58. '20' => array(self::NUMERIC, 2, 2, false),
  59. '21' => array(self::ALPHA_NUMERIC, 1, 20, false),
  60. '240' => array(self::ALPHA_NUMERIC, 1, 30, false),
  61. '241' => array(self::ALPHA_NUMERIC, 1, 30, false),
  62. '250' => array(self::ALPHA_NUMERIC, 1, 30, false),
  63. '251' => array(self::ALPHA_NUMERIC, 1, 30, false),
  64. '253' => array(self::NUMERIC, 14, 30, false),
  65. '30' => array(self::NUMERIC, 1, 8, false),
  66. '310y' => array(self::NUMERIC, 6, 6, false),
  67. '311y' => array(self::NUMERIC, 6, 6, false),
  68. '312y' => array(self::NUMERIC, 6, 6, false),
  69. '313y' => array(self::NUMERIC, 6, 6, false),
  70. '314y' => array(self::NUMERIC, 6, 6, false),
  71. '315y' => array(self::NUMERIC, 6, 6, false),
  72. '316y' => array(self::NUMERIC, 6, 6, false),
  73. '320y' => array(self::NUMERIC, 6, 6, false),
  74. '321y' => array(self::NUMERIC, 6, 6, false),
  75. '322y' => array(self::NUMERIC, 6, 6, false),
  76. '323y' => array(self::NUMERIC, 6, 6, false),
  77. '324y' => array(self::NUMERIC, 6, 6, false),
  78. '325y' => array(self::NUMERIC, 6, 6, false),
  79. '326y' => array(self::NUMERIC, 6, 6, false),
  80. '327y' => array(self::NUMERIC, 6, 6, false),
  81. '328y' => array(self::NUMERIC, 6, 6, false),
  82. '329y' => array(self::NUMERIC, 6, 6, false),
  83. '330y' => array(self::NUMERIC, 6, 6, false),
  84. '331y' => array(self::NUMERIC, 6, 6, false),
  85. '332y' => array(self::NUMERIC, 6, 6, false),
  86. '333y' => array(self::NUMERIC, 6, 6, false),
  87. '334y' => array(self::NUMERIC, 6, 6, false),
  88. '335y' => array(self::NUMERIC, 6, 6, false),
  89. '336y' => array(self::NUMERIC, 6, 6, false),
  90. '337y' => array(self::NUMERIC, 6, 6, false),
  91. '340y' => array(self::NUMERIC, 6, 6, false),
  92. '341y' => array(self::NUMERIC, 6, 6, false),
  93. '342y' => array(self::NUMERIC, 6, 6, false),
  94. '343y' => array(self::NUMERIC, 6, 6, false),
  95. '344y' => array(self::NUMERIC, 6, 6, false),
  96. '345y' => array(self::NUMERIC, 6, 6, false),
  97. '346y' => array(self::NUMERIC, 6, 6, false),
  98. '347y' => array(self::NUMERIC, 6, 6, false),
  99. '348y' => array(self::NUMERIC, 6, 6, false),
  100. '349y' => array(self::NUMERIC, 6, 6, false),
  101. '350y' => array(self::NUMERIC, 6, 6, false),
  102. '351y' => array(self::NUMERIC, 6, 6, false),
  103. '352y' => array(self::NUMERIC, 6, 6, false),
  104. '353y' => array(self::NUMERIC, 6, 6, false),
  105. '354y' => array(self::NUMERIC, 6, 6, false),
  106. '355y' => array(self::NUMERIC, 6, 6, false),
  107. '356y' => array(self::NUMERIC, 6, 6, false),
  108. '357y' => array(self::NUMERIC, 6, 6, false),
  109. '360y' => array(self::NUMERIC, 6, 6, false),
  110. '361y' => array(self::NUMERIC, 6, 6, false),
  111. '362y' => array(self::NUMERIC, 6, 6, false),
  112. '363y' => array(self::NUMERIC, 6, 6, false),
  113. '364y' => array(self::NUMERIC, 6, 6, false),
  114. '365y' => array(self::NUMERIC, 6, 6, false),
  115. '366y' => array(self::NUMERIC, 6, 6, false),
  116. '367y' => array(self::NUMERIC, 6, 6, false),
  117. '368y' => array(self::NUMERIC, 6, 6, false),
  118. '369y' => array(self::NUMERIC, 6, 6, false),
  119. '37' => array(self::NUMERIC, 1, 8, false),
  120. '390y' => array(self::NUMERIC, 1, 15, false),
  121. '391y' => array(self::NUMERIC, 4, 18, false),
  122. '392y' => array(self::NUMERIC, 1, 15, false),
  123. '393y' => array(self::NUMERIC, 4, 18, false),
  124. '400' => array(self::ALPHA_NUMERIC, 1, 30, false),
  125. '401' => array(self::ALPHA_NUMERIC, 1, 30, false),
  126. '402' => array(self::NUMERIC, 17, 17, false),
  127. '403' => array(self::ALPHA_NUMERIC, 1, 30, false),
  128. '410' => array(self::NUMERIC, 13, 13, true),
  129. '411' => array(self::NUMERIC, 13, 13, true),
  130. '412' => array(self::NUMERIC, 13, 13, true),
  131. '413' => array(self::NUMERIC, 13, 13, true),
  132. '414' => array(self::NUMERIC, 13, 13, true),
  133. '415' => array(self::NUMERIC, 13, 13, true),
  134. '420' => array(self::ALPHA_NUMERIC, 1, 20, false),
  135. '421' => array(self::ALPHA_NUMERIC, 4, 12, false),
  136. '422' => array(self::NUMERIC, 3, 3, false),
  137. '8001' => array(self::NUMERIC, 14, 14, false),
  138. '8002' => array(self::ALPHA_NUMERIC, 1, 20, false),
  139. '8003' => array(self::ALPHA_NUMERIC, 15, 30, false),
  140. '8004' => array(self::ALPHA_NUMERIC, 1, 30, false),
  141. '8005' => array(self::NUMERIC, 6, 6, false),
  142. '8006' => array(self::NUMERIC, 18, 18, false),
  143. '8007' => array(self::ALPHA_NUMERIC, 1, 30, false),
  144. '8018' => array(self::NUMERIC, 18, 18, false),
  145. '8020' => array(self::ALPHA_NUMERIC, 1, 25, false),
  146. '8100' => array(self::NUMERIC, 6, 6, false),
  147. '8101' => array(self::NUMERIC, 10, 10, false),
  148. '8102' => array(self::NUMERIC, 2, 2, false),
  149. '90' => array(self::ALPHA_NUMERIC, 1, 30, false),
  150. '91' => array(self::ALPHA_NUMERIC, 1, 30, false),
  151. '92' => array(self::ALPHA_NUMERIC, 1, 30, false),
  152. '93' => array(self::ALPHA_NUMERIC, 1, 30, false),
  153. '94' => array(self::ALPHA_NUMERIC, 1, 30, false),
  154. '95' => array(self::ALPHA_NUMERIC, 1, 30, false),
  155. '96' => array(self::ALPHA_NUMERIC, 1, 30, false),
  156. '97' => array(self::ALPHA_NUMERIC, 1, 30, false),
  157. '98' => array(self::ALPHA_NUMERIC, 1, 30, false),
  158. '99' => array(self::ALPHA_NUMERIC, 1, 30, false)
  159. );
  160. $this->setStrictMode(true);
  161. $this->setTilde(true);
  162. $this->setAllowsUnknownIdentifier(false);
  163. $this->setNoLengthLimit(false);
  164. }
  165. /**
  166. * Gets the content checksum for an identifier.
  167. * Do not pass the identifier code.
  168. *
  169. * @param string $content
  170. * @return int
  171. */
  172. public static function getAiContentChecksum($content) {
  173. return self::calculateChecksumMod10($content);
  174. }
  175. /**
  176. * Enables or disables the strict mode.
  177. *
  178. * @param bool $strictMode
  179. */
  180. public function setStrictMode($strictMode) {
  181. $this->strictMode = $strictMode;
  182. }
  183. /**
  184. * Gets if the strict mode is activated.
  185. *
  186. * @return bool
  187. */
  188. public function getStrictMode() {
  189. return $this->strictMode;
  190. }
  191. /**
  192. * Allows unknown identifiers.
  193. *
  194. * @param bool $allow
  195. */
  196. public function setAllowsUnknownIdentifier($allow) {
  197. $this->allowsUnknownIdentifier = (bool)$allow;
  198. }
  199. /**
  200. * Gets if unkmown identifiers are allowed.
  201. *
  202. * @return bool
  203. */
  204. public function getAllowsUnknownIdentifier() {
  205. return $this->allowsUnknownIdentifier;
  206. }
  207. /**
  208. * Removes the limit of 48 characters.
  209. *
  210. * @param bool $noLengthLimit
  211. */
  212. public function setNoLengthLimit($noLengthLimit) {
  213. $this->noLengthLimit = (bool)$noLengthLimit;
  214. }
  215. /**
  216. * Gets if the limit of 48 characters is removed.
  217. *
  218. * @return bool
  219. */
  220. public function getNoLengthLimit() {
  221. return $this->noLengthLimit;
  222. }
  223. /**
  224. * Parses Text.
  225. *
  226. * @param string $text
  227. */
  228. public function parse($text) {
  229. parent::parse($this->parseGs1128($text));
  230. }
  231. /**
  232. * Formats data for gs1-128.
  233. *
  234. * @return string
  235. */
  236. private function formatGs1128() {
  237. $formatedText = '~F1';
  238. $formatedLabel = '';
  239. $c = count($this->identifiersId);
  240. for ($i = 0; $i < $c; $i++) {
  241. if ($i > 0) {
  242. $formatedLabel .= ' ';
  243. }
  244. if ($this->identifiersId[$i] !== null) {
  245. $formatedLabel .= '(' . $this->identifiersId[$i] . ')';
  246. }
  247. $formatedText .= $this->identifiersId[$i];
  248. $formatedLabel .= $this->identifiersContent[$i];
  249. $formatedText .= $this->identifiersContent[$i];
  250. if (isset($this->identifiersAi[$this->identifiersId[$i]])) {
  251. $ai_data = $this->identifiersAi[$this->identifiersId[$i]];
  252. } elseif (isset($this->identifiersId[$i][3])) {
  253. $identifierWithVar = substr($this->identifiersId[$i], 0, -1) . 'y';
  254. $ai_data = isset($this->identifiersAi[$identifierWithVar]) ? $this->identifiersAi[$identifierWithVar] : null;
  255. } else {
  256. $ai_data = null;
  257. }
  258. /* We'll check if we need to add a ~F1 (<GS>) char */
  259. /* If we use the legacy mode, we always add a ~F1 (<GS>) char between AIs */
  260. if ($ai_data !== null) {
  261. if ((strlen($this->identifiersContent[$i]) < $ai_data[self::MAXLENGTH] && ($i + 1) !== $c) || (!$this->strictMode && ($i + 1) !== $c)) {
  262. $formatedText .= '~F1';
  263. }
  264. } elseif ($this->allowsUnknownIdentifier && $this->identifiersId[$i] === null && ($i + 1) !== $c) {
  265. /* If this id is unknown, we add a ~F1 (<GS>) char */
  266. $formatedText .= '~F1';
  267. }
  268. }
  269. if ($this->noLengthLimit === false && (strlen(str_replace('~F1', chr(29), $formatedText)) - 1) > self::MAX_GS1128_CHARS) {
  270. throw new BCGParseException('gs1128', 'The barcode can\'t contain more than ' . self::MAX_GS1128_CHARS . ' characters.');
  271. }
  272. $this->label = $formatedLabel;
  273. return $formatedText;
  274. }
  275. /**
  276. * Parses the text to gs1-128.
  277. *
  278. * @param mixed $text
  279. * @return mixed
  280. */
  281. private function parseGs1128($text) {
  282. /* We format correctly what the user gives */
  283. if (is_array($text)) {
  284. $formatArray = array();
  285. foreach ($text as $content) {
  286. if (is_array($content)) { /* double array */
  287. if (count($content) === 2) {
  288. if (is_array($content[self::ID]) || is_array($content[self::CONTENT])) {
  289. throw new BCGParseException('gs1128', 'Double arrays can\'t contain arrays.');
  290. } else {
  291. $formatArray[] = '(' . $content[self::ID] . ')' . $content[self::CONTENT];
  292. }
  293. } else {
  294. throw new BCGParseException('gs1128', 'Double arrays must contain 2 values.');
  295. }
  296. } else { /* simple array */
  297. $formatArray[] = $content;
  298. }
  299. }
  300. unset($text);
  301. $text = $formatArray;
  302. } else { /* string */
  303. $text = array($text);
  304. }
  305. $textCount = count($text);
  306. for ($cmpt = 0; $cmpt < $textCount; $cmpt++) {
  307. /* We parse the content of the array */
  308. if (!$this->parseContent($text[$cmpt])) {
  309. return;
  310. }
  311. }
  312. return $this->formatGs1128();
  313. }
  314. /**
  315. * Splits the id and the content for each application identifiers (AIs).
  316. *
  317. * @param string $text
  318. * @param int $cmpt
  319. * @return bool
  320. */
  321. private function parseContent($text) {
  322. /* $yAlreadySet has 3 states: */
  323. /* null: There is no variable in the ID; true: the variable is already set; false: the variable is not set yet; */
  324. $content = null;
  325. $yAlreadySet = null;
  326. $realNameId = null;
  327. $separatorsFound = 0;
  328. $checksumAdded = 0;
  329. $decimalPointRemoved = 0;
  330. $toParse = str_replace('~F1', chr(29), $text);
  331. $nbCharToParse = strlen($toParse);
  332. $nbCharId = 0;
  333. $isFormated = $toParse[0] === '(' ? true : false;
  334. $maxCharId = $isFormated ? self::MAX_ID_FORMATED : self::MAX_ID_NOT_FORMATED;
  335. $id = strtolower(substr($toParse, 0, min($maxCharId, $nbCharToParse)));
  336. $id = $isFormated ? $this->findIdFormated($id, $yAlreadySet, $realNameId) : $this->findIdNotFormated($id, $yAlreadySet, $realNameId);
  337. if ($id === false) {
  338. if ($this->allowsUnknownIdentifier === false) {
  339. return false;
  340. }
  341. $id = null;
  342. $nbCharId = 0;
  343. $content = $toParse;
  344. } else {
  345. $nbCharId = strlen($id) + ($isFormated ? 2 : 0);
  346. $n = min($this->identifiersAi[$realNameId][self::MAXLENGTH], $nbCharToParse);
  347. $content = substr($toParse, $nbCharId, $n);
  348. }
  349. if ($id !== null) {
  350. /* If we have an AI with an "y" var, we check if there is a decimal point in the next *MAXLENGTH* characters */
  351. /* if there is one, we take an extra character */
  352. if ($yAlreadySet !== null) {
  353. if (strpos($content, '.') !== false || strpos($content, ',') !== false) {
  354. $n++;
  355. if ($n <= $nbCharToParse) {
  356. /* We take an extra char */
  357. $content = substr($toParse, $nbCharId, $n);
  358. }
  359. }
  360. }
  361. }
  362. /* We check for separator */
  363. $separator = strpos($content, chr(29));
  364. if ($separator !== false) {
  365. $content = substr($content, 0, $separator);
  366. $separatorsFound++;
  367. }
  368. if ($id !== null) {
  369. /* We check the conformity */
  370. if (!$this->checkConformity($content, $id, $realNameId)) {
  371. return false;
  372. }
  373. /* We check the checksum */
  374. if (!$this->checkChecksum($content, $id, $realNameId, $checksumAdded)) {
  375. return false;
  376. }
  377. /* We check the vars */
  378. if (!$this->checkVars($content, $id, $yAlreadySet, $decimalPointRemoved)) {
  379. return false;
  380. }
  381. }
  382. $this->identifiersId[] = $id;
  383. $this->identifiersContent[] = $content;
  384. $nbCharLastContent = (((strlen($content) + $nbCharId) - $checksumAdded) + $decimalPointRemoved) + $separatorsFound;
  385. if ($nbCharToParse - $nbCharLastContent > 0) {
  386. /* If there is more than one content in this array, we parse again */
  387. $otherContent = substr($toParse, $nbCharLastContent, $nbCharToParse);
  388. $nbCharOtherContent = strlen($otherContent);
  389. if ($otherContent[0] === chr(29)) {
  390. $otherContent = substr($otherContent, 1);
  391. $nbCharOtherContent--;
  392. }
  393. if ($nbCharOtherContent > 0) {
  394. $text = $otherContent;
  395. return $this->parseContent($text);
  396. }
  397. }
  398. return true;
  399. }
  400. /**
  401. * Checks if an id exists.
  402. *
  403. * @param string $id
  404. * @param bool $yAlreadySet
  405. * @param string $realNameId
  406. * @return bool
  407. */
  408. private function idExists($id, &$yAlreadySet, &$realNameId) {
  409. $yFound = isset($id[3]) && $id[3] === 'y';
  410. $idVarAdded = substr($id, 0, -1) . 'y';
  411. if (isset($this->identifiersAi[$id])) {
  412. if ($yFound) {
  413. $yAlreadySet = false;
  414. }
  415. $realNameId = $id;
  416. return true;
  417. } elseif (!$yFound && isset($this->identifiersAi[$idVarAdded])) {
  418. /* if the id don't exist, we try to find this id with "y" at the last char */
  419. $yAlreadySet = true;
  420. $realNameId = $idVarAdded;
  421. return true;
  422. }
  423. return false;
  424. }
  425. /**
  426. * Finds ID with formated content.
  427. *
  428. * @param string $id
  429. * @param bool $yAlreadySet
  430. * @param string $realNameId
  431. * @return mixed
  432. */
  433. private function findIdFormated($id, &$yAlreadySet, &$realNameId) {
  434. $pos = strpos($id, ')');
  435. if ($pos === false) {
  436. throw new BCGParseException('gs1128', 'Identifiers must have no more than 4 characters.');
  437. } else {
  438. if ($pos < 3) {
  439. throw new BCGParseException('gs1128', 'Identifiers must have at least 2 characters.');
  440. }
  441. $id = substr($id, 1, $pos - 1);
  442. if ($this->idExists($id, $yAlreadySet, $realNameId)) {
  443. return $id;
  444. }
  445. if ($this->allowsUnknownIdentifier === false) {
  446. throw new BCGParseException('gs1128', 'The identifier ' . $id . ' doesn\'t exist.');
  447. }
  448. return false;
  449. }
  450. }
  451. /**
  452. * Finds ID with non-formated content.
  453. *
  454. * @param string $id
  455. * @param bool $yAlreadySet
  456. * @param string $realNameId
  457. * @return mixed
  458. */
  459. private function findIdNotFormated($id, &$yAlreadySet, &$realNameId) {
  460. $tofind = $id;
  461. while (strlen($tofind) >= 2) {
  462. if ($this->idExists($tofind, $yAlreadySet, $realNameId)) {
  463. return $tofind;
  464. } else {
  465. $tofind = substr($tofind, 0, -1);
  466. }
  467. }
  468. if ($this->allowsUnknownIdentifier === false) {
  469. throw new BCGParseException('gs1128', 'Error in formatting, can\'t find an identifier.');
  470. }
  471. return false;
  472. }
  473. /**
  474. * Checks confirmity of the content.
  475. *
  476. * @param string $content
  477. * @param string $id
  478. * @param string $realNameId
  479. * @return bool
  480. */
  481. private function checkConformity(&$content, $id, $realNameId) {
  482. switch ($this->identifiersAi[$realNameId][self::KIND_OF_DATA]) {
  483. case self::NUMERIC:
  484. $content = str_replace(',', '.', $content);
  485. if (!preg_match("/^[0-9.]+$/", $content)) {
  486. throw new BCGParseException('gs1128', 'The value of "' . $id . '" must be numerical.');
  487. }
  488. break;
  489. case self::DATE_YYMMDD:
  490. $valid_date = true;
  491. if (preg_match("/^[0-9]{6}$/", $content)) {
  492. $year = substr($content, 0, 2);
  493. $month = substr($content, 2, 2);
  494. $day = substr($content, 4, 2);
  495. /* day can be 00 if we only need month and year */
  496. if (intval($month) < 1 || intval($month) > 12 || intval($day) < 0 || intval($day) > 31) {
  497. $valid_date = false;
  498. }
  499. } else {
  500. $valid_date = false;
  501. }
  502. if (!$valid_date) {
  503. throw new BCGParseException('gs1128', 'The value of "' . $id . '" must be in YYMMDD format.');
  504. }
  505. break;
  506. }
  507. // We check the length of the content
  508. $nbCharContent = strlen($content);
  509. $checksumChar = 0;
  510. $minlengthContent = $this->identifiersAi[$realNameId][self::MINLENGTH];
  511. $maxlengthContent = $this->identifiersAi[$realNameId][self::MAXLENGTH];
  512. if ($this->identifiersAi[$realNameId][self::CHECKSUM]) {
  513. $checksumChar++;
  514. }
  515. if ($nbCharContent < ($minlengthContent - $checksumChar)) {
  516. if ($minlengthContent === $maxlengthContent) {
  517. throw new BCGParseException('gs1128', 'The value of "' . $id . '" must contain ' . $minlengthContent . ' character(s).');
  518. } else {
  519. throw new BCGParseException('gs1128', 'The value of "' . $id . '" must contain between ' . $minlengthContent . ' and ' . $maxlengthContent . ' character(s).');
  520. }
  521. }
  522. return true;
  523. }
  524. /**
  525. * Verifies the checksum.
  526. *
  527. * @param string $content
  528. * @param string $id
  529. * @param int $realNameId
  530. * @param int $checksumAdded
  531. * @return bool
  532. */
  533. private function checkChecksum(&$content, $id, $realNameId, &$checksumAdded) {
  534. if ($this->identifiersAi[$realNameId][self::CHECKSUM]) {
  535. $nbCharContent = strlen($content);
  536. $minlengthContent = $this->identifiersAi[$realNameId][self::MINLENGTH];
  537. if ($nbCharContent === ($minlengthContent - 1)) {
  538. /* we need to calculate the checksum */
  539. $content .= self::getAiContentChecksum($content);
  540. $checksumAdded++;
  541. } elseif ($nbCharContent === $minlengthContent) {
  542. /* we need to check the checksum */
  543. $checksum = self::getAiContentChecksum(substr($content, 0, -1));
  544. if (intval($content[$nbCharContent - 1]) !== $checksum) {
  545. throw new BCGParseException('gs1128', 'The checksum of "(' . $id . ') ' . $content . '" must be: ' . $checksum);
  546. }
  547. }
  548. }
  549. return true;
  550. }
  551. /**
  552. * Checks vars "y".
  553. *
  554. * @param string $content
  555. * @param string $id
  556. * @param bool $yAlreadySet
  557. * @param int $decimalPointRemoved
  558. * @return bool
  559. */
  560. private function checkVars(&$content, &$id, $yAlreadySet, &$decimalPointRemoved) {
  561. $nbCharContent = strlen($content);
  562. /* We check for "y" var in AI */
  563. if ($yAlreadySet) {
  564. /* We'll check if we have a decimal point */
  565. if (strpos($content, '.') !== false) {
  566. throw new BCGParseException('gs1128', 'If you do not use any "y" variable, you have to insert a whole number.');
  567. }
  568. } elseif ($yAlreadySet !== null) {
  569. /* We need to replace the "y" var with the position of the decimal point */
  570. $pos = strpos($content, '.');
  571. if ($pos === false) {
  572. $pos = $nbCharContent - 1;
  573. }
  574. $id = str_replace('y', $nbCharContent - ($pos + 1), strtolower($id));
  575. $content = str_replace('.', '', $content);
  576. $decimalPointRemoved++;
  577. }
  578. return true;
  579. }
  580. /**
  581. * Checksum Mod10.
  582. *
  583. * @param int $content
  584. * @return int
  585. */
  586. private static function calculateChecksumMod10($content) {
  587. // Calculating Checksum
  588. // Consider the right-most digit of the message to be in an "odd" position,
  589. // and assign odd/even to each character moving from right to left
  590. // Odd Position = 3, Even Position = 1
  591. // Multiply it by the number
  592. // Add all of that and do 10-(?mod10)
  593. $odd = true;
  594. $checksumValue = 0;
  595. $c = strlen($content);
  596. for ($i = $c; $i > 0; $i--) {
  597. if ($odd === true) {
  598. $multiplier = 3;
  599. $odd = false;
  600. } else {
  601. $multiplier = 1;
  602. $odd = true;
  603. }
  604. $checksumValue += ($content[$i - 1] * $multiplier);
  605. }
  606. return (10 - $checksumValue % 10) % 10;
  607. }
  608. }
  609. ?>