Subversion Repositories oidplus

Rev

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