Subversion Repositories oidplus

Rev

Rev 1121 | Rev 1139 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
635 daniel-mar 1
<?php
2
 
3
/*
4
 * OIDplus 2.0
1086 daniel-mar 5
 * Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft
635 daniel-mar 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
 
1050 daniel-mar 20
namespace ViaThinkSoft\OIDplus;
635 daniel-mar 21
 
1086 daniel-mar 22
// phpcs:disable PSR1.Files.SideEffects
23
\defined('INSIDE_OIDPLUS') or die;
24
// phpcs:enable PSR1.Files.SideEffects
25
 
635 daniel-mar 26
class OIDplusGs1 extends OIDplusObject {
1130 daniel-mar 27
        /**
28
         * @var int|string
29
         */
635 daniel-mar 30
        private $number;
31
 
1116 daniel-mar 32
        /**
1130 daniel-mar 33
         * @param string|int $number
1116 daniel-mar 34
         */
635 daniel-mar 35
        public function __construct($number) {
36
                // TODO: syntax checks
37
                $this->number = $number;
38
        }
39
 
1116 daniel-mar 40
        /**
41
         * @param string $node_id
42
         * @return OIDplusGs1|null
43
         */
44
        public static function parse(string $node_id)/*: ?OIDplusGs1*/ {
635 daniel-mar 45
                @list($namespace, $number) = explode(':', $node_id, 2);
1116 daniel-mar 46
                if ($namespace !== self::ns()) return null;
635 daniel-mar 47
                return new self($number);
48
        }
49
 
1116 daniel-mar 50
        /**
51
         * @return string
52
         */
53
        public static function objectTypeTitle(): string {
635 daniel-mar 54
                return _L('GS1 Based IDs (GLN/GTIN/SSCC/...)');
55
        }
56
 
1116 daniel-mar 57
        /**
58
         * @return string
59
         */
60
        public static function objectTypeTitleShort(): string {
635 daniel-mar 61
                return _L('GS1');
62
        }
63
 
1116 daniel-mar 64
        /**
65
         * @return string
66
         */
67
        public static function ns(): string {
635 daniel-mar 68
                return 'gs1';
69
        }
70
 
1116 daniel-mar 71
        /**
72
         * @return string
73
         */
74
        public static function root(): string {
860 daniel-mar 75
                return self::ns().':';
635 daniel-mar 76
        }
77
 
1116 daniel-mar 78
        /**
79
         * @return bool
80
         */
81
        public function isRoot(): bool {
635 daniel-mar 82
                return $this->number == '';
83
        }
84
 
1116 daniel-mar 85
        /**
86
         * @param bool $with_ns
87
         * @return string
88
         */
89
        public function nodeId(bool $with_ns=true): string {
859 daniel-mar 90
                return $with_ns ? self::root().$this->number : $this->number;
635 daniel-mar 91
        }
92
 
1116 daniel-mar 93
        /**
94
         * @param string $str
95
         * @return string
96
         * @throws OIDplusException
97
         */
98
        public function addString(string $str): string {
635 daniel-mar 99
                $m = array();
100
                if (!preg_match('@^\\d+$@', $str, $m)) {
101
                        throw new OIDplusException(_L('GS1 value needs to be numeric'));
102
                }
103
 
104
                return $this->nodeId() . $str;
105
        }
106
 
1116 daniel-mar 107
        /**
108
         * @param OIDplusObject $parent
109
         * @return string
1130 daniel-mar 110
         * @throws OIDplusException
1116 daniel-mar 111
         */
112
        public function crudShowId(OIDplusObject $parent): string {
635 daniel-mar 113
                return $this->chunkedNotation(false);
114
        }
115
 
1116 daniel-mar 116
        /**
117
         * @return string
1130 daniel-mar 118
         * @throws OIDplusException
1116 daniel-mar 119
         */
120
        public function crudInsertPrefix(): string {
635 daniel-mar 121
                return $this->isRoot() ? '' : $this->chunkedNotation(false);
122
        }
123
 
1116 daniel-mar 124
        /**
125
         * @param OIDplusObject|null $parent
126
         * @return string
127
         */
128
        public function jsTreeNodeName(OIDplusObject $parent = null): string {
635 daniel-mar 129
                if ($parent == null) return $this->objectTypeTitle();
130
                return substr($this->nodeId(), strlen($parent->nodeId()));
131
        }
132
 
1116 daniel-mar 133
        /**
134
         * @return string
135
         */
136
        public function defaultTitle(): string {
635 daniel-mar 137
                return $this->number;
138
        }
139
 
1116 daniel-mar 140
        /**
141
         * @return bool
142
         */
143
        public function isLeafNode(): bool {
635 daniel-mar 144
                return !$this->isBaseOnly();
145
        }
146
 
1116 daniel-mar 147
        /**
148
         * @param string $title
149
         * @param string $content
150
         * @param string $icon
151
         * @return void
152
         * @throws OIDplusException
153
         */
154
        public function getContentPage(string &$title, string &$content, string &$icon) {
801 daniel-mar 155
                $icon = file_exists(__DIR__.'/img/main_icon.png') ? OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/main_icon.png' : '';
635 daniel-mar 156
 
157
                if ($this->isRoot()) {
158
                        $title = OIDplusGs1::objectTypeTitle();
159
 
160
                        $res = OIDplus::db()->query("select * from ###objects where parent = ?", array(self::root()));
790 daniel-mar 161
                        if ($res->any()) {
962 daniel-mar 162
                                $content  = '<p>'._L('Please select an item in the tree view at the left to show its contents.').'</p>';
635 daniel-mar 163
                        } else {
962 daniel-mar 164
                                $content  = '<p>'._L('Currently, no GS1 based numbers are registered in the system.').'</p>';
635 daniel-mar 165
                        }
166
 
167
                        if (!$this->isLeafNode()) {
168
                                if (OIDplus::authUtils()->isAdminLoggedIn()) {
169
                                        $content .= '<h2>'._L('Manage root objects').'</h2>';
170
                                } else {
171
                                        $content .= '<h2>'._L('Available objects').'</h2>';
172
                                }
173
                                $content .= '%%CRUD%%';
174
                        }
175
                } else {
176
                        $title = $this->getTitle();
177
 
178
                        if ($this->isLeafNode()) {
179
                                $chunked = $this->chunkedNotation(true);
180
                                $checkDigit = $this->checkDigit();
181
                                $content = '<h2>'.$chunked.' - <abbr title="'._L('check digit').'">'.$checkDigit.'</abbr></h2>';
182
                                $content .= '<p><a target="_blank" href="https://www.ean-search.org/?q='.htmlentities($this->fullNumber()).'">'._L('Lookup at ean-search.org').'</a></p>';
801 daniel-mar 183
                                $content .= '<img src="'.OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'barcode.php?number='.urlencode($this->fullNumber()).'">';
635 daniel-mar 184
                                $content .= '<h2>'._L('Description').'</h2>%%DESC%%'; // TODO: add more meta information about the object type
185
                        } else {
186
                                $chunked = $this->chunkedNotation(true);
187
                                $content = '<h2>'.$chunked.'</h2>';
188
                                $content .= '<h2>'._L('Description').'</h2>%%DESC%%'; // TODO: add more meta information about the object type
189
                                if ($this->userHasWriteRights()) {
928 daniel-mar 190
                                        $content .= '<h2>'._L('Create or change subordinate objects').'</h2>';
635 daniel-mar 191
                                } else {
928 daniel-mar 192
                                        $content .= '<h2>'._L('Subordinate objects').'</h2>';
635 daniel-mar 193
                                }
194
                                $content .= '%%CRUD%%';
195
                        }
196
                }
197
        }
198
 
199
        # ---
200
 
1116 daniel-mar 201
        /**
202
         * @return bool
203
         */
204
        public function isBaseOnly(): bool {
635 daniel-mar 205
                return strlen($this->number) <= 7;
206
        }
207
 
1116 daniel-mar 208
        /**
209
         * @param bool $withAbbr
210
         * @return string
211
         * @throws OIDplusException
212
         */
213
        public function chunkedNotation(bool $withAbbr=true): string {
859 daniel-mar 214
                $curid = self::root().$this->number;
635 daniel-mar 215
 
977 daniel-mar 216
                $obj = OIDplusObject::findFitting($curid);
217
                if (!$obj) return $this->number;
635 daniel-mar 218
 
219
                $hints = array();
220
                $lengths = array(strlen($curid));
977 daniel-mar 221
                while ($obj = OIDplusObject::findFitting($curid)) {
222
                        $objParent = $obj->getParent();
223
                        if (!$objParent) break;
224
                        $curid = $objParent->nodeId();
225
                        $hints[] = $obj->getTitle();
635 daniel-mar 226
                        $lengths[] = strlen($curid);
227
                }
228
 
229
                array_shift($lengths);
230
                $chunks = array();
231
 
859 daniel-mar 232
                $full = self::root().$this->number;
635 daniel-mar 233
                foreach ($lengths as $len) {
234
                        $chunks[] = substr($full, $len);
235
                        $full = substr($full, 0, $len);
236
                }
237
 
238
                $hints = array_reverse($hints);
239
                $chunks = array_reverse($chunks);
240
 
241
                $full = array();
242
                foreach ($chunks as $c) {
934 daniel-mar 243
                        $hint = array_shift($hints);
244
                        $full[] = $withAbbr && ($hint !== '') ? '<abbr title="'.htmlentities($hint).'">'.$c.'</abbr>' : $c;
635 daniel-mar 245
                }
246
                return implode(' ', $full);
247
        }
248
 
1116 daniel-mar 249
        /**
250
         * @return string
251
         */
252
        public function fullNumber(): string {
635 daniel-mar 253
                return $this->number . $this->checkDigit();
254
        }
255
 
1116 daniel-mar 256
        /**
257
         * @return int
258
         */
259
        public function checkDigit(): int {
635 daniel-mar 260
                $mul = 3;
261
                $sum = 0;
262
                for ($i=strlen($this->number)-1; $i>=0; $i--) {
1130 daniel-mar 263
                        $str = "".$this->number;
264
                        $sum += (int)$str[$i] * $mul;
635 daniel-mar 265
                        $mul = $mul == 3 ? 1 : 3;
266
                }
267
                return 10 - ($sum % 10);
268
        }
269
 
1116 daniel-mar 270
        /**
271
         * @return OIDplusGs1|null
272
         */
273
        public function one_up()/*: ?OIDplusGs1*/ {
274
                return self::parse($this->ns().':'.substr($this->number,0,strlen($this->number)-1));
635 daniel-mar 275
        }
276
 
1116 daniel-mar 277
        /**
278
         * @param string $a
279
         * @param string $b
280
         * @return false|int
281
         */
282
        private static function distance_(string $a, string $b) {
635 daniel-mar 283
                $min_len = min(strlen($a), strlen($b));
284
 
285
                for ($i=0; $i<$min_len; $i++) {
286
                        if ($a[$i] != $b[$i]) return false;
287
                }
288
 
289
                return strlen($a) - strlen($b);
290
        }
291
 
1116 daniel-mar 292
        /**
1130 daniel-mar 293
         * @param OIDplusObject|string $to
1116 daniel-mar 294
         * @return int|null
295
         */
635 daniel-mar 296
        public function distance($to) {
297
                if (!is_object($to)) $to = OIDplusObject::parse($to);
1121 daniel-mar 298
                if (!$to) return null;
1116 daniel-mar 299
                if (!($to instanceof $this)) return null;
635 daniel-mar 300
 
301
                // This is pretty tricky, because the whois service should accept GS1 numbers with and without checksum
302
                if ($this->number == $to->number) return 0;
303
                if ($this->number.$this->checkDigit() == $to->number) return 0;
304
                if ($this->number == $to->number.$to->checkDigit()) return 0;
305
 
306
                $b = $this->number;
307
                $a = $to->number;
308
                $tmp = self::distance_($a, $b);
1116 daniel-mar 309
                if ($tmp !== false) return $tmp;
635 daniel-mar 310
 
311
                $b = $this->number.$this->checkDigit();
312
                $a = $to->number;
313
                $tmp = self::distance_($a, $b);
1116 daniel-mar 314
                if ($tmp !== false) return $tmp;
635 daniel-mar 315
 
316
                $b = $this->number;
317
                $a = $to->number.$to->checkDigit();
318
                $tmp = self::distance_($a, $b);
1116 daniel-mar 319
                if ($tmp !== false) return $tmp;
635 daniel-mar 320
 
321
                return null;
322
        }
323
 
1116 daniel-mar 324
        /**
325
         * @return array|OIDplusAltId[]
326
         * @throws OIDplusException
327
         */
328
        public function getAltIds(): array {
945 daniel-mar 329
                if ($this->isRoot()) return array();
330
                $ids = parent::getAltIds();
331
 
332
                // (VTS F5) GS1 to AID (PIX allowed)
333
                $gs1 = $this->nodeId(false);
334
                $aid = 'D276000186F5'.$gs1;
335
                if (strlen($aid)%2 == 1) $aid .= 'F';
336
                $aid_is_ok = aid_canonize($aid);
959 daniel-mar 337
                if ($aid_is_ok) $ids[] = new OIDplusAltId('aid', $aid, _L('Application Identifier (ISO/IEC 7816)'), ' ('._L('Optional PIX allowed, with "FF" prefix').')');
945 daniel-mar 338
 
339
                return $ids;
340
        }
341
 
1116 daniel-mar 342
        /**
343
         * @return string
344
         */
345
        public function getDirectoryName(): string {
635 daniel-mar 346
                if ($this->isRoot()) return $this->ns();
347
                return $this->ns().'_'.$this->nodeId(false); // safe, because there are only numbers
348
        }
800 daniel-mar 349
 
1116 daniel-mar 350
        /**
351
         * @param string $mode
352
         * @return string
353
         */
354
        public static function treeIconFilename(string $mode): string {
800 daniel-mar 355
                return 'img/'.$mode.'_icon16.png';
356
        }
635 daniel-mar 357
}