Subversion Repositories oidplus

Rev

Rev 977 | Rev 1086 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /*
  4.  * OIDplus 2.0
  5.  * Copyright 2019 - 2022 Daniel Marschall, ViaThinkSoft
  6.  *
  7.  * Licensed under the Apache License, Version 2.0 (the "License");
  8.  * you may not use this file except in compliance with the License.
  9.  * You may obtain a copy of the License at
  10.  *
  11.  *     http://www.apache.org/licenses/LICENSE-2.0
  12.  *
  13.  * Unless required by applicable law or agreed to in writing, software
  14.  * distributed under the License is distributed on an "AS IS" BASIS,
  15.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16.  * See the License for the specific language governing permissions and
  17.  * limitations under the License.
  18.  */
  19.  
  20. namespace ViaThinkSoft\OIDplus;
  21.  
  22. class OIDplusGs1 extends OIDplusObject {
  23.         private $number;
  24.  
  25.         public function __construct($number) {
  26.                 // TODO: syntax checks
  27.                 $this->number = $number;
  28.         }
  29.  
  30.         public static function parse($node_id) {
  31.                 @list($namespace, $number) = explode(':', $node_id, 2);
  32.                 if ($namespace !== self::ns()) return false;
  33.                 return new self($number);
  34.         }
  35.  
  36.         public static function objectTypeTitle() {
  37.                 return _L('GS1 Based IDs (GLN/GTIN/SSCC/...)');
  38.         }
  39.  
  40.         public static function objectTypeTitleShort() {
  41.                 return _L('GS1');
  42.         }
  43.  
  44.         public static function ns() {
  45.                 return 'gs1';
  46.         }
  47.  
  48.         public static function root() {
  49.                 return self::ns().':';
  50.         }
  51.  
  52.         public function isRoot() {
  53.                 return $this->number == '';
  54.         }
  55.  
  56.         public function nodeId($with_ns=true) {
  57.                 return $with_ns ? self::root().$this->number : $this->number;
  58.         }
  59.  
  60.         public function addString($str) {
  61.                 $m = array();
  62.                 if (!preg_match('@^\\d+$@', $str, $m)) {
  63.                         throw new OIDplusException(_L('GS1 value needs to be numeric'));
  64.                 }
  65.  
  66.                 return $this->nodeId() . $str;
  67.         }
  68.  
  69.         public function crudShowId(OIDplusObject $parent) {
  70.                 return $this->chunkedNotation(false);
  71.         }
  72.  
  73.         public function crudInsertPrefix() {
  74.                 return $this->isRoot() ? '' : $this->chunkedNotation(false);
  75.         }
  76.  
  77.         public function jsTreeNodeName(OIDplusObject $parent = null) {
  78.                 if ($parent == null) return $this->objectTypeTitle();
  79.                 return substr($this->nodeId(), strlen($parent->nodeId()));
  80.         }
  81.  
  82.         public function defaultTitle() {
  83.                 return $this->number;
  84.         }
  85.  
  86.         public function isLeafNode() {
  87.                 return !$this->isBaseOnly();
  88.         }
  89.  
  90.         public function getContentPage(&$title, &$content, &$icon) {
  91.                 $icon = file_exists(__DIR__.'/img/main_icon.png') ? OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/main_icon.png' : '';
  92.  
  93.                 if ($this->isRoot()) {
  94.                         $title = OIDplusGs1::objectTypeTitle();
  95.  
  96.                         $res = OIDplus::db()->query("select * from ###objects where parent = ?", array(self::root()));
  97.                         if ($res->any()) {
  98.                                 $content  = '<p>'._L('Please select an item in the tree view at the left to show its contents.').'</p>';
  99.                         } else {
  100.                                 $content  = '<p>'._L('Currently, no GS1 based numbers are registered in the system.').'</p>';
  101.                         }
  102.  
  103.                         if (!$this->isLeafNode()) {
  104.                                 if (OIDplus::authUtils()->isAdminLoggedIn()) {
  105.                                         $content .= '<h2>'._L('Manage root objects').'</h2>';
  106.                                 } else {
  107.                                         $content .= '<h2>'._L('Available objects').'</h2>';
  108.                                 }
  109.                                 $content .= '%%CRUD%%';
  110.                         }
  111.                 } else {
  112.                         $title = $this->getTitle();
  113.  
  114.                         if ($this->isLeafNode()) {
  115.                                 $chunked = $this->chunkedNotation(true);
  116.                                 $checkDigit = $this->checkDigit();
  117.                                 $content = '<h2>'.$chunked.' - <abbr title="'._L('check digit').'">'.$checkDigit.'</abbr></h2>';
  118.                                 $content .= '<p><a target="_blank" href="https://www.ean-search.org/?q='.htmlentities($this->fullNumber()).'">'._L('Lookup at ean-search.org').'</a></p>';
  119.                                 $content .= '<img src="'.OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'barcode.php?number='.urlencode($this->fullNumber()).'">';
  120.                                 $content .= '<h2>'._L('Description').'</h2>%%DESC%%'; // TODO: add more meta information about the object type
  121.                         } else {
  122.                                 $chunked = $this->chunkedNotation(true);
  123.                                 $content = '<h2>'.$chunked.'</h2>';
  124.                                 $content .= '<h2>'._L('Description').'</h2>%%DESC%%'; // TODO: add more meta information about the object type
  125.                                 if ($this->userHasWriteRights()) {
  126.                                         $content .= '<h2>'._L('Create or change subordinate objects').'</h2>';
  127.                                 } else {
  128.                                         $content .= '<h2>'._L('Subordinate objects').'</h2>';
  129.                                 }
  130.                                 $content .= '%%CRUD%%';
  131.                         }
  132.                 }
  133.         }
  134.  
  135.         # ---
  136.  
  137.         public function isBaseOnly() {
  138.                 return strlen($this->number) <= 7;
  139.         }
  140.  
  141.         public function chunkedNotation($withAbbr=true) {
  142.                 $curid = self::root().$this->number;
  143.  
  144.                 $obj = OIDplusObject::findFitting($curid);
  145.                 if (!$obj) return $this->number;
  146.  
  147.                 $hints = array();
  148.                 $lengths = array(strlen($curid));
  149.                 while ($obj = OIDplusObject::findFitting($curid)) {
  150.                         $objParent = $obj->getParent();
  151.                         if (!$objParent) break;
  152.                         $curid = $objParent->nodeId();
  153.                         $hints[] = $obj->getTitle();
  154.                         $lengths[] = strlen($curid);
  155.                 }
  156.  
  157.                 array_shift($lengths);
  158.                 $chunks = array();
  159.  
  160.                 $full = self::root().$this->number;
  161.                 foreach ($lengths as $len) {
  162.                         $chunks[] = substr($full, $len);
  163.                         $full = substr($full, 0, $len);
  164.                 }
  165.  
  166.                 $hints = array_reverse($hints);
  167.                 $chunks = array_reverse($chunks);
  168.  
  169.                 $full = array();
  170.                 foreach ($chunks as $c) {
  171.                         $hint = array_shift($hints);
  172.                         $full[] = $withAbbr && ($hint !== '') ? '<abbr title="'.htmlentities($hint).'">'.$c.'</abbr>' : $c;
  173.                 }
  174.                 return implode(' ', $full);
  175.         }
  176.  
  177.         public function fullNumber() {
  178.                 return $this->number . $this->checkDigit();
  179.         }
  180.  
  181.         public function checkDigit() {
  182.                 $mul = 3;
  183.                 $sum = 0;
  184.                 for ($i=strlen($this->number)-1; $i>=0; $i--) {
  185.                         $sum += $this->number[$i] * $mul;
  186.                         $mul = $mul == 3 ? 1 : 3;
  187.                 }
  188.                 return 10 - ($sum % 10);
  189.         }
  190.  
  191.         public function one_up() {
  192.                 return OIDplusObject::parse($this->ns().':'.substr($this->number,0,strlen($this->number)-1));
  193.         }
  194.  
  195.         private static function distance_($a, $b) {
  196.                 $min_len = min(strlen($a), strlen($b));
  197.  
  198.                 for ($i=0; $i<$min_len; $i++) {
  199.                         if ($a[$i] != $b[$i]) return false;
  200.                 }
  201.  
  202.                 return strlen($a) - strlen($b);
  203.         }
  204.  
  205.         public function distance($to) {
  206.                 if (!is_object($to)) $to = OIDplusObject::parse($to);
  207.                 if (!($to instanceof $this)) return false;
  208.  
  209.                 // This is pretty tricky, because the whois service should accept GS1 numbers with and without checksum
  210.                 if ($this->number == $to->number) return 0;
  211.                 if ($this->number.$this->checkDigit() == $to->number) return 0;
  212.                 if ($this->number == $to->number.$to->checkDigit()) return 0;
  213.  
  214.                 $b = $this->number;
  215.                 $a = $to->number;
  216.                 $tmp = self::distance_($a, $b);
  217.                 if ($tmp != false) return $tmp;
  218.  
  219.                 $b = $this->number.$this->checkDigit();
  220.                 $a = $to->number;
  221.                 $tmp = self::distance_($a, $b);
  222.                 if ($tmp != false) return $tmp;
  223.  
  224.                 $b = $this->number;
  225.                 $a = $to->number.$to->checkDigit();
  226.                 $tmp = self::distance_($a, $b);
  227.                 if ($tmp != false) return $tmp;
  228.  
  229.                 return null;
  230.         }
  231.  
  232.         public function getAltIds() {
  233.                 if ($this->isRoot()) return array();
  234.                 $ids = parent::getAltIds();
  235.  
  236.                 // (VTS F5) GS1 to AID (PIX allowed)
  237.                 $gs1 = $this->nodeId(false);
  238.                 $aid = 'D276000186F5'.$gs1;
  239.                 if (strlen($aid)%2 == 1) $aid .= 'F';
  240.                 $aid_is_ok = aid_canonize($aid);
  241.                 if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid, _L('Application Identifier (ISO/IEC 7816)'), ' ('._L('Optional PIX allowed, with "FF" prefix').')');
  242.  
  243.                 return $ids;
  244.         }
  245.  
  246.         public function getDirectoryName() {
  247.                 if ($this->isRoot()) return $this->ns();
  248.                 return $this->ns().'_'.$this->nodeId(false); // safe, because there are only numbers
  249.         }
  250.  
  251.         public static function treeIconFilename($mode) {
  252.                 return 'img/'.$mode.'_icon16.png';
  253.         }
  254. }
  255.