Subversion Repositories oidplus

Rev

Rev 250 | Go to most recent revision | Blame | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /*
  4.  * OIDplus 2.0
  5.  * Copyright 2019 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. if (!defined('IN_OIDPLUS')) die();
  21.  
  22. class OIDplusObjectTypePluginGs1 extends OIDplusObjectTypePlugin {
  23.  
  24.         public static function getPluginInformation() {
  25.                 $out = array();
  26.                 $out['name'] = 'GS1 Based IDs';
  27.                 $out['author'] = 'ViaThinkSoft';
  28.                 $out['version'] = null;
  29.                 $out['descriptionHTML'] = null;
  30.                 return $out;
  31.         }
  32.  
  33.         public static function getObjectTypeClassName() {
  34.                 return 'OIDplusGs1';
  35.         }
  36.  
  37. }
  38.  
  39. class OIDplusGs1 extends OIDplusObject {
  40.         private $number;
  41.  
  42.         public function __construct($number) {
  43.                 // TODO: syntax checks
  44.                 $this->number = $number;
  45.         }
  46.  
  47.         public static function parse($node_id) {
  48.                 @list($namespace, $number) = explode(':', $node_id, 2);
  49.                 if ($namespace !== 'gs1') return false;
  50.                 return new self($number);
  51.         }
  52.  
  53.         public static function objectTypeTitle() {
  54.                 return "GS1 Based IDs (GLN/GTIN/SSCC/...)";
  55.         }
  56.  
  57.         public static function objectTypeTitleShort() {
  58.                 return "GS1";
  59.         }
  60.  
  61.         public static function ns() {
  62.                 return 'gs1';
  63.         }
  64.  
  65.         public static function root() {
  66.                 return 'gs1:';
  67.         }
  68.  
  69.         public function isRoot() {
  70.                 return $this->number == '';
  71.         }
  72.  
  73.         public function nodeId($with_ns=true) {
  74.                 return $with_ns ? 'gs1:'.$this->number : $this->number;
  75.         }
  76.  
  77.         public function addString($str) {
  78.                 if (!preg_match('@^\\d+$@', $str, $m)) {
  79.                         throw new OIDplusException('GS1 value needs to be numeric');
  80.                 }
  81.  
  82.                 return $this->nodeId() . $str;
  83.         }
  84.  
  85.         public function crudShowId(OIDplusObject $parent) {
  86.                 return $this->chunkedNotation(false);
  87.         }
  88.  
  89.         public function crudInsertPrefix() {
  90.                 return $this->isRoot() ? '' : $this->chunkedNotation(false);
  91.         }
  92.  
  93.         public function jsTreeNodeName(OIDplusObject $parent = null) {
  94.                 if ($parent == null) return $this->objectTypeTitle();
  95.                 return substr($this->nodeId(), strlen($parent->nodeId()));
  96.         }
  97.  
  98.         public function defaultTitle() {
  99.                 return $this->number;
  100.         }
  101.  
  102.         public function isLeafNode() {
  103.                 return !$this->isBaseOnly();
  104.         }
  105.  
  106.         public function getContentPage(&$title, &$content, &$icon) {
  107.                 $icon = file_exists(__DIR__.'/icon_big.png') ? 'plugins/objectTypes/'.basename(__DIR__).'/icon_big.png' : '';
  108.  
  109.                 if ($this->isRoot()) {
  110.                         $title = OIDplusGs1::objectTypeTitle();
  111.  
  112.                         $res = OIDplus::db()->query("select * from ###objects where parent = ?", array(self::root()));
  113.                         if ($res->num_rows() > 0) {
  114.                                 $content  = 'Please select an item in the tree view at the left to show its contents.';
  115.                         } else {
  116.                                 $content  = 'Currently, no GS1 based numbers are registered in the system.';
  117.                         }
  118.  
  119.                         if (!$this->isLeafNode()) {
  120.                                 if (OIDplus::authUtils()::isAdminLoggedIn()) {
  121.                                         $content .= '<h2>Manage root objects</h2>';
  122.                                 } else {
  123.                                         $content .= '<h2>Available objects</h2>';
  124.                                 }
  125.                                 $content .= '%%CRUD%%';
  126.                         }
  127.                 } else {
  128.                         $title = $this->getTitle();
  129.  
  130.                         if ($this->isLeafNode()) {
  131.                                 $chunked = $this->chunkedNotation(true);
  132.                                 $checkDigit = $this->checkDigit();
  133.                                 $content = '<h2>'.$chunked.' - <abbr title="check digit">'.$checkDigit.'</abbr></h2>';
  134.                                 $content .= '<p><a target="_blank" href="https://www.ean-search.org/?q='.htmlentities($this->fullNumber()).'">Lookup in ean-search.org</a></p>';
  135.                                 $content .= '<img src="plugins/objectTypes/'.basename(__DIR__).'/barcode.php?number='.urlencode($this->fullNumber()).'">';
  136.                                 $content .= '<h2>Description</h2>%%DESC%%'; // TODO: add more meta information about the object type
  137.                         } else {
  138.                                 $chunked = $this->chunkedNotation(true);
  139.                                 $content = '<h2>'.$chunked.'</h2>';
  140.                                 $content .= '<h2>Description</h2>%%DESC%%'; // TODO: add more meta information about the object type
  141.                                 if ($this->userHasWriteRights()) {
  142.                                         $content .= '<h2>Create or change subsequent objects</h2>';
  143.                                 } else {
  144.                                         $content .= '<h2>Subsequent objects</h2>';
  145.                                 }
  146.                                 $content .= '%%CRUD%%';
  147.                         }
  148.                 }
  149.         }
  150.  
  151.         # ---
  152.  
  153.         public function isBaseOnly() {
  154.                 return strlen($this->number) <= 7;
  155.         }
  156.  
  157.         public function chunkedNotation($withAbbr=true) {
  158.                 $curid = 'gs1:'.$this->number;
  159.  
  160.                 $res = OIDplus::db()->query("select id, title from ###objects where id = ?", array($curid));
  161.                 if ($res->num_rows() == 0) return $this->number();
  162.  
  163.                 $hints = array();
  164.                 $lengths = array(strlen($curid));
  165.                 while (($res = OIDplus::db()->query("select parent, title from ###objects where id = ?", array($curid)))->num_rows() > 0) {
  166.                         $row = $res->fetch_array();
  167.                         $curid = $row['parent'];
  168.                         $hints[] = $row['title'];
  169.                         $lengths[] = strlen($curid);
  170.                 }
  171.  
  172.                 array_shift($lengths);
  173.                 $chunks = array();
  174.  
  175.                 $full = 'gs1:'.$this->number;
  176.                 foreach ($lengths as $len) {
  177.                         $chunks[] = substr($full, $len);
  178.                         $full = substr($full, 0, $len);
  179.                 }
  180.  
  181.                 $hints = array_reverse($hints);
  182.                 $chunks = array_reverse($chunks);
  183.  
  184.                 $full = array();
  185.                 foreach ($chunks as $c) {
  186.                         $full[] = $withAbbr ? '<abbr title="'.htmlentities(array_shift($hints)).'">'.$c.'</abbr>' : $c;
  187.                 }
  188.                 return implode(' ', $full);
  189.         }
  190.  
  191.         public function fullNumber() {
  192.                 return $this->number . $this->checkDigit();
  193.         }
  194.  
  195.         public function checkDigit() {
  196.                 $mul = 3;
  197.                 $sum = 0;
  198.                 for ($i=strlen($this->number)-1; $i>=0; $i--) {
  199.                         $sum += $this->number[$i] * $mul;
  200.                         $mul = $mul == 3 ? 1 : 3;
  201.                 }
  202.                 return 10 - ($sum % 10);
  203.         }
  204.  
  205.         public function one_up() {
  206.                 return  OIDplusObject::parse($this->ns().':'.substr($this->number,0,strlen($this->number)-1));
  207.         }
  208.  
  209.         private static function distance_($a, $b) {
  210.                 $min_len = min(strlen($a), strlen($b));
  211.  
  212.                 for ($i=0; $i<$min_len; $i++) {
  213.                         if ($a[$i] != $b[$i]) return false;
  214.                 }
  215.  
  216.                 return strlen($a) - strlen($b);
  217.         }
  218.  
  219.         public function distance($to) {
  220.                 if (!is_object($to)) $to = OIDplusObject::parse($to);
  221.                 if (!($to instanceof $this)) return false;
  222.  
  223.                 // This is pretty tricky, because the whois service should accept GS1 numbers with and without checksum
  224.                 if ($this->number == $to->number) return 0;
  225.                 if ($this->number.$this->checkDigit() == $to->number) return 0;
  226.                 if ($this->number == $to->number.$to->checkDigit()) return 0;
  227.  
  228.                 $b = $this->number;
  229.                 $a = $to->number;
  230.                 $tmp = self::distance_($a, $b);
  231.                 if ($tmp != false) return $tmp;
  232.  
  233.                 $b = $this->number.$this->checkDigit();
  234.                 $a = $to->number;
  235.                 $tmp = self::distance_($a, $b);
  236.                 if ($tmp != false) return $tmp;
  237.  
  238.                 $b = $this->number;
  239.                 $a = $to->number.$to->checkDigit();
  240.                 $tmp = self::distance_($a, $b);
  241.                 if ($tmp != false) return $tmp;
  242.  
  243.                 return null;
  244.         }
  245. }
  246.