ISBN::_isbnVersionIsValid()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
nc 4
nop 1
dl 0
loc 15
rs 9.7666
c 0
b 0
f 0
ccs 0
cts 14
cp 0
crap 20
1
<?php
2
/**
3
 * ISBN
4
 *
5
 * Handle, Convert and Validate ISBN Numbers
6
 *
7
 * PHP version 5
8
 *
9
 * LICENSE: LGPL (In cases LGPL is not appropriate, it is licensed under GPL)
10
 *
11
 * Package to handle, convert and validate ISBN numbers. It includes:
12
 *
13
 *  - ISBN specifics: EAN/PrefixArrayAccess (integer)
14
 *  - ISBN specifics: Group/Registration Group [2001: Group identifier] (integer)
15
 *  - ISBN specifics: GroupTitle/Registration Group Title (string)
16
 *  - ISBN specifics: Publisher/Registrant [2001: Publisher identifier] (string)
17
 *  - ISBN specifics: Title/Publication [2001: Title identifier] (string)
18
 *  - ISBN specifics: Checkdigit (string)
19
 *  - ISBN specifics: 'ISBNBody' (string)
20
 *  - ISBN specifics: 'ISBNSubbody' (string)
21
 *  - ISBN Version handling
22
 *  - Syntactical Validation plus Validation based on real ISBN Data
23
 *  - ISBN-10 (ISO 2108) checksum calculation
24
 *  - Validation (ISBN-10 and ISBN-13-978)
25
 *  - Conversion to ISBN-13-978
26
 *  - ISBN-13-978 (2005 Handbook, ISO pending; ISBN-13)
27
 *  - ISBN-13 checksum calculation (EAN)
28
 *
29
 * Based on standards published by international ISBN Agency
30
 * http://www.isbn-international.org/
31
 *
32
 * @category  Pending
33
 * @package   ISBN
34
 * @author    Tom Klingenberg <[email protected]>
35
 * @copyright 2006-2007 Tom Klingenberg
36
 * @license   LGPL http://www.gnu.org/licenses/lgpl.txt
37
 * @version   v 0.1.6 CVS: <cvs_id>
38
 * @link      http://isbn.lastflood.com online docs
39
 *
40
 * @todo      License for .js file or remove it
41
 * @todo      GroupTitle
42
 * @todo      PEAR Package.xml
43
 *
44
 */
45
46
// {{{ constants
47
/**
48
 * ISBN Versions supported
49
 */
50
define('ISBN_VERSION_NONE', false);
51
/**
52
 * VERSION_UNKNOWN is by the caller only, this shall never
53
 * be a value returned by a public function or getter
54
 */
55
define('ISBN_VERSION_UNKNOWN', 0);
56
define('ISBN_VERSION_ISBN_10', 10);
57
define('ISBN_VERSION_ISBN_13', 13978);
58
define('ISBN_VERSION_ISBN_13_978', ISBN_VERSION_ISBN_13);
59
define('ISBN_VERSION_ISBN_13_979', 13979);  /* reserved */
60
61
/*
62
 * Default ISBN Version for class input / usage
63
 */
64
define('ISBN_DEFAULT_INPUTVERSION', ISBN_VERSION_UNKNOWN);
65
/*
66
 * Default ISBN Seperator string
67
 */
68
define('ISBN_DEFAULT_COSMETIC_SEPERATOR', '-');
69
70
/*
71
 * ISBN_DEFAULT_PRINT_LANG_SPECIFIC_PREFIX
72
 *
73
 * When printed, the ISBN is always preceded by the letters "ISBN".
74
 * Note: In countries where the Latin alphabet is not used, an abbreviation
75
 * in the characters of the local script may be used in addition to the
76
 * Latin letters "ISBN".
77
 * This can be defined as a default value wihtin this constant.
78
 */
79
define('ISBN_DEFAULT_PRINT_LANG_SPECIFIC_PREFIX', '');
80
// }}}
81
82
// {{{ ISBN_Exception
83
/**
84
 * ISBN_Exception class
85
 *
86
 * @category  Pending
87
 * @package   ISBN
88
 * @author    Tom Klingenberg <[email protected]>
89
 * @copyright 2006-2007 Tom Klingenberg
90
 * @license   LGPL http://www.gnu.org/licenses/lgpl.txt
91
 * @link      http://isbn.lastflood.com/
92
 * @since     Class available since Release 0.1.3
93
 */
94
class ISBN_Exception
95
{
96
  function __construct($message, $e = null) {
97
    $this->message = $message;
0 ignored issues
show
Bug introduced by
The property message does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
98
    $this->e = $e;
0 ignored issues
show
Bug introduced by
The property e does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
99
  }
100
101
  function getMessage() {
102
    return $this->message;
103
  }
104
}
105
// }}}
106
107
// {{{ ISBN
108
/**
109
 * ISBN class
110
 *
111
 * Class to Handle, Convert and Validate ISBN Numbers
112
 *
113
 * @category  Pending
114
 * @package   ISBN
115
 * @author    Tom Klingenberg <[email protected]>
116
 * @copyright 2006-2007 Tom Klingenberg
117
 * @license   LGPL http://www.gnu.org/licenses/lgpl.txt
118
 * @link      http://isbn.lastflood.com/
119
 * @since     Class available since Release 0.0.0
120
 */
121
class ISBN
122
{
123
	var $groups_csv = "data/groups.csv";
124
125
    /**
126
     * @var string ISBN Registration Group
127
     */
128
    var $isbn_group = '';
129
    /**
130
     * @var string ISBN Publisher
131
     */
132
    var $isbn_publisher = '';
133
    /**
134
     * @var string ISBN Title
135
     */
136
    var $isbn_title = '';
137
138
    /**
139
     * @var mixed ISBN number version
140
     */
141
    var $ver = ISBN_VERSION_NONE;
142
143
    /**
144
     * @var array ISBN Groups Data acting as cache
145
     * @see _getISBN10Groups()
146
     */
147
    var $varISBN10Groups = array();
148
149
    // {{{ __construct
150
    /**
151
     * Constructor
152
     *
153
     * @param array $isbn String of ISBN Value to use
154
     * @param mixed $ver  Optional Version Constant
155
     *
156
     * @access public
157
     *
158
     * @throws ISBN_Exception in case it fails
159
     */
160
    function __construct($isbn = '', $ver = ISBN_DEFAULT_INPUTVERSION)
161
    {
162
        /* validate & handle optional isbn parameter */
163
        if (is_string($isbn) == false ) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
164
            return new ISBN_Exception('ISBN parameter must be a string');
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
165
        }
166
        if (strlen($isbn) == 0) {
167
            $this->setISBN($isbn);
168
            return;
169
        }
170
171
        /* validate version parameter */
172
        if (ISBN::_isbnVersionIs($ver) == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
173
            return new ISBN_Exception(
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
174
                'ISBN Version parameter is not an ISBN Version'
175
            );
176
        }
177
178
        /* ISBN has been passed, check the version now:
179
         *  if it is unknown, try to dertine it, if this fails
180
         *  throw an exception
181
         */
182
        if ($ver == ISBN_VERSION_UNKNOWN) {
183
            $verguess = ISBN::_isbnVersionGuess($isbn);
184
            if (ISBN::_isbnVersionIsValid($verguess)) {
185
                $ver = $verguess;
186
            } else {
187
                /* throw new ISBN_Exception(
188
                 *'ISBN Version couldn\'t determined.');
189
                 */
190
                $ver = ISBN_VERSION_NONE;
191
            }
192
        }
193
        /* version determined */
194
        $this->ver = $ver;
195
196
        /* handle a complete invalid ISBN of which a version could
197
         * not be determined. */
198
        if ($ver === ISBN_VERSION_NONE) {
199
            $this->setISBN('');
200
            return;
201
        }
202
203
        $e =  $this->setISBN($isbn);
204
        if ("ISBN_Exception" == get_class($e)) {
205
            /* the isbn is invalid and not set, sothat this
206
             * ISBN object will be set to a blank value. */
207
            $this->setISBN('');
208
        }
209
210
    }
211
    // }}}
212
213
    // {{{ _extractCheckdigit()
214
    /**
215
     * extract Checkdigit of an ISBN-Number
216
     *
217
     * @param string $isbnn normalized ISBN string
218
     *
219
     * @return string|false ISBN-Body or false if failed
220
     *
221
     */
222
    function _extractCheckdigit($isbnn)
223
    {
224
        $checkdigit = false;
0 ignored issues
show
Unused Code introduced by
$checkdigit is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
225
        $checkdigit = substr($isbnn, -1);
226
        return (string) $checkdigit;
227
    }
228
    // }}}
229
230
    // {{{ _extractEANPrefix()
231
    /**
232
     * extracts EAN-Prefix of a normalized isbn string
233
     *
234
     * @param string $isbnn normalized isbn string
235
     *
236
     * @return string|false Prefix or false if failed
237
     */
238
    function _extractEANPrefix($isbnn)
239
    {
240
        $r = settype($isbnn, 'string');
241
        if ($r === false) {
242
            return false;
243
        }
244
        if (strlen($isbnn) < 3) {
245
            return false;
246
        }
247
        $prefix = substr($isbnn, 0, 3);
248
        return $prefix;
249
    }
250
    // }}}
251
252
    // {{{ _extractGroup()
253
    /**
254
     * extract Registration Group of an ISBN-Body
255
     *
256
     * @param string $isbnbody ISBN-Body
257
     *
258
     * @return integer|false    Registration Group or false if failed
259
     */
260
    function _extractGroup($isbnbody)
261
    {
262
        $group   = '';
263
        $subbody = '';
264
265
        $r = ISBN::_isbnBodyParts($isbnbody, $group, $subbody);
266
        if ($r === false) {
267
            return false;
268
        }
269
        return $group;
270
    }
271
    // }}}
272
273
    // {{{ _extractISBNBody()
274
    /**
275
     * extract ISBN-Body of an ISBN-Number
276
     *
277
     * @param string $isbnn normalized ISBN string
278
     *
279
     * @return string|false ISBN-Body or false if failed
280
     */
281
    function _extractISBNBody($isbnn)
282
    {
283
        /* extract */
284
        $body  = false;
0 ignored issues
show
Unused Code introduced by
$body is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
285
        $isbnn = (string) $isbnn;
286
287
        $l = strlen($isbnn);
288
        if ($l == 10) {
289
            $body =  substr($isbnn, 0, -1);
290
        } elseif ($l == 13) {
291
            $body =  substr($isbnn, 3, -1);
292
        } else {
293
            return false;
294
        }
295
        /* verify */
296
        $r = settype($body, 'string');
297
        if ($r === false) {
298
            return false;
299
        }
300
        if (strlen($body) != 9) {
301
            return false;
302
        }
303
        if (ctype_digit($body) === false) {
304
            return false;
305
        }
306
        return $body;
307
    }
308
    // }}}
309
310
    // {{{ _isbnBodyParts()
311
    /**
312
     * Get the 2 Parts of the ISBN-Body (ISBN-10/ISBN-13-978)
313
     *
314
     * @param string $isbnbody           ISBN-Body
315
     * @param string &$registrationgroup Registration Group
316
     * @param string &$isbnsubbody       ISBN-Subbody
317
     *
318
     * @return boolean  False if failed, True on success
319
     *
320
     * @access private
321
     */
322
    function _isbnBodyParts($isbnbody,
323
                                           &$registrationgroup,
324
                                           &$isbnsubbody)
325
    {
326
        /* validate input (should not be needed, @access private) */
327
        $r = settype($isbnbody, 'string');
328
        if ($r === false) {
329
            return false;
330
        }
331
        if (strlen($isbnbody) != 9) {
332
            return false;
333
        }
334
        if (ctype_digit($isbnbody) === false) {
335
            return false;
336
        }
337
        /* extract registraion group
338
         * boundaries see p.13 2005 handbook
339
         */
340
        $boundaries = array();
341
342
        $boundaries[] = array(    0, 59999, 1);
343
        $boundaries[] = array(60000, 60099, 3); // Iran 2006-12-05
344
        $boundaries[] = array(60100, 69999, 0);
345
        $boundaries[] = array(70000, 79999, 1);
346
        $boundaries[] = array(80000, 94999, 2);
347
        $boundaries[] = array(95000, 98999, 3);
348
        $boundaries[] = array(99000, 99899, 4);
349
        $boundaries[] = array(99900, 99999, 5);
350
        /* segment value */
351
        $segment      = substr($isbnbody, 0, 5);
352
        $segmentvalue = intval($segment);
353
        /* test segment value against boundaries */
354
        $r = false;
355
        foreach ($boundaries as $boundary) {
356
            if ($segmentvalue >= $boundary[0] && $segmentvalue <= $boundary[1]) {
357
                $r = $boundary[2];
358
            }
359
        }
360
        if ($r === false) {
361
            return false;
362
        }
363
        /* $r is 0 when the boundary is not defined */
364
        if ($r === 0) {
365
            return false;
366
        }
367
        $registrationgroup = substr($isbnbody, 0, $r);
368
        $isbnsubbody       = substr($isbnbody, $r);
369
        return true;
370
    }
371
    // }}}
372
373
    // {{{ _isbnSubbodyParts()
374
    /**
375
     * Get the 2 Parts of the ISBN-Subbody (ISBN-10/ISBN-13)
376
     *
377
     * @param string  $isbnsubbody  ISBN-Subbody
378
     * @param integer $groupid      Registrationgroup
379
     * @param string  &$registrant  Registrant
380
     * @param string  &$publication Publication
381
     *
382
     * @return boolean  False if failed, true on success
383
     *
384
     * @access private
385
     */
386
    function _isbnSubbodyParts($isbnsubbody,
387
                                              $groupid,
388
                                              &$registrant,
389
                                              &$publication)
390
    {
391
        /* validate input (should not be needed, @access private) */
392
        $r = settype($isbnsubbody, 'string');
393
        if ($r === false) {
394
            return false;
395
        }
396
        $l = strlen($isbnsubbody);
397
        if ( $l < 1 || $l > 8) {
398
            return false;
399
        }
400
        if (ctype_digit($isbnsubbody) === false) {
401
            return false;
402
        }
403
        $r = settype($groupid, 'integer');
404
        if ($r === false) {
405
            return false;
406
        }
407
        if ($groupid < 0 || $groupid > 99999) {
408
            return false;
409
        }
410
        /* extract registrant based on group and registrant range
411
         * parse this specific group format:
412
         *  array(
413
         *      'English speaking area',
414
         *      '00-09;10-19;200-699;7000-8499;85000-89999;' .
415
         *         '900000-949999;9500000-9999999'
416
         *      );
417
         */
418
419
        $group = ISBN::_getISBN10Group($groupid);
420
        if ($group === false) {
421
            return false;
422
        }
423
424
        $len = ISBN::_getRegistrantLength($isbnsubbody, $group[1]);
425
        if ($len === false) {
426
            return false;
427
        }
428
        $registrant  = substr($isbnsubbody, 0, $len);
429
        $publication = substr($isbnsubbody, $len);
430
        return true;
431
    }
432
    // }}}
433
434
    // {{{ _getRegistrantLength()
435
    /**
436
     * Return Length of Registrant part within an ISBNSubbody in a specific
437
     * grouprange in this specific format:
438
     *
439
     * '00-09;10-19;200-699;7000-8499;85000-89999;900000-949999;9500000-9999999'
440
     *
441
     * Info: This function is compatible with Groupranges formatted in the
442
     * .js file and might become obsolete if new formats are more fitting.
443
     *
444
     * @param string $isbnsubbody ISBN-Subbody
445
     * @param string $grouprange  Grouprange in the Format '#a1-#z1;#a2-z2[...]'
446
     *
447
     * @return boolean|int  False if failed or Length (in chars) of Registrant
448
     *
449
     * @access private
450
     */
451
    function _getRegistrantLength($isbnsubbody, $grouprange)
452
    {
453
        $r = settype($grouprange, 'string');
454
        if ($r === false) {
455
            return false;
456
        }
457
        if (strlen($grouprange) < 3) {
458
            return false;
459
        }
460
461
        $sl     = strlen($isbnsubbody);
462
        $ranges = explode(';', $grouprange);
463
        foreach ($ranges as $range) {
464
            $range  = trim($range);
465
            $fromto = explode('-', $range);
466
            if (count($fromto) !== 2) {
467
                return false;
468
            }
469
            /* validation:
470
             * from and to need to be in the same class,
471
             * having the same length.
472
             * registrant can not be bigger or same then the
473
             * whole subbody, at least there is one digit for
474
             * the publication.
475
             */
476
477
            $l = strlen($fromto[0]);
478
            if ($l != strlen($fromto[1])) {
479
                return false;
480
            }
481
            if ($l >= $sl) {
482
                return false;
483
            }
484
485
            /* check that from/to values are in order */
486
            if (strcmp($fromto[0], $fromto[1]) >= 0) {
487
                return false;
488
            }
489
490
            /* compare and fire if matched */
491
            $comparec = substr($isbnsubbody, 0, $l);
492
493
            if (strcmp($fromto[0], $comparec) < 1 &&
494
                strcmp($fromto[1], $comparec) > -1) {
495
                return $l;
496
            }
497
        }
498
        return false;
499
    }
500
    // }}}
501
502
    // {{{ _getISBN10Group()
503
    /**
504
     * Get ISBN-10 Registration Group Data by its numeric ID
505
     *
506
     * @param integer $id Registration Group Identifier
507
     *
508
     * @return mixed    array:   group array
509
     *                  boolean: False if failed
510
     */
511
    function _getISBN10Group($id)
512
    {
513
        $r = settype($id, 'integer');
514
        if ($r === false) {
515
            return false;
516
        }
517
        $groups = ISBN::_getISBN10Groups();
518
        if ($groups === false) {
519
            return false;
520
        }
521
        if (isset($groups[$id]) === false) {
522
            return false;
523
        }
524
        $group = $groups[$id];
525
        return $group;
526
    }
527
    // }}}
528
529
    // {{{ _getISBN10Groups()
530
    /**
531
     * Get all ISBN-10 Registration Groups
532
     *
533
     * @return array    groups array
534
     *
535
     * Info: This function connects outer world data into this class logic
536
     *       which can be generated with the supplied tools.
537
     *       A user should not alter the array data. This data should be altered
538
     *       together with the international ISBN Agency only.
539
     */
540
    function _getISBN10Groups()
541
    {
542
        /* check if data has been already loaded */
543
        if (sizeof($this->varISBN10Groups) > 0 ) {
544
                return $this->varISBN10Groups;
545
        }
546
547
        /* load external data */
548
	$t = file_get_contents($this->groups_csv);
549
        /* parse external data */
550
        $groups = array();
551
        $tls    = explode("\n", $t);
552
        $line   = 0;
553
        foreach ($tls as $tl) {
554
            $line++;
555
            $tlp = explode(',', $tl);
556
            if (sizeof($tlp) == 3) {
557
                $index = intval($tlp[0]);
558
                if (isset($groups[$index])) {
559
                    return new ISBN_Exception(
560
                        'ISBN Groups Data is invalid, Group ' .
561
                        $index . 'is a duplicate.'
562
                    );
563
                }
564
                /* edit+ mature: sanitize external
565
                   data */
566
                $groups[$index] = array($tlp[1],$tlp[2]);
567
            } else {
568
                return new ISBN_Exception(
569
                    'ISBN Groups Data is malformed on line #' . $line .
570
                    ' (' . sizeof($tlp) . ').'
571
                );
572
            }
573
        }
574
575
        /* verify minimum */
576
        if (sizeof($groups) == 0 ) {
577
            return new ISBN_Exception(
578
                'ISBN Groups Data does not contain any valid data.'
579
            );
580
        }
581
582
        $this->varISBN10Groups = $groups;
583
584
        /* return groups */
585
        return $groups;
586
    }
587
    // }}}
588
589
    // {{{ _getVersion()
590
    /**
591
     * Get the Version of am ISBN Number
592
     *
593
     * @param string $isbn ISBN Number ofwhich the version to get
594
     *
595
     * @return mixed false for no, or fully identifyable ISBN
596
     *                              Version Constant
597
     *
598
     * @access private
599
     */
600
    function _getVersion($isbn)
601
    {
602
        $ver = ISBN::_isbnVersionGuess($isbn);
603
        $r   = ISBN::_isbnVersionIsValid($ver);
604
        return $r;
605
    }
606
    // }}}
607
608
    // {{{ _checkdigitISBN10()
609
     /**
610
     * Calculate checkdigit of an ISBN-10 string (ISBN-Body)
611
     * as documented on pp.4-5 2001 handbook.
612
     *
613
     * @param string $isbnbody ISBN-Body
614
     *
615
     * @return string|false Checkdigit [0-9,X] or false if failed
616
     *
617
     * @access private
618
     */
619
    function _checkdigitISBN10($isbnbody)
620
    {
621
        /* The check digit is the last digit of an ISBN. It is calculated
622
         * on a modulus 11 with weights 10-2, using X in lieu of 10 where
623
         * ten would occur as a check digit.
624
         * This means that each of the first nine digits of the ISBN �
625
         * excluding the check digit itself � is multiplied by a number
626
         * ranging from 10 to 2 and that the resulting sum of the products,
627
         * plus the check digit, must be divisible by 11 without a
628
         * remainder. (pp.4-5 2001 Handbook)
629
         */
630
        if (strlen($isbnbody) != 9) {
631
            return false;
632
        }
633
        $sum = 0;
634 View Code Duplication
        for ($i = 0; $i < 10; $i++) {
635
            $v    = intval(substr($isbnbody, $i, 1));
636
            $sum += $v * (10 - $i);
637
        }
638
        $remainder  = $sum % 11;
639
        $checkdigit = 11 - $remainder;
640
        if ($remainder == 0) {
641
            $checkdigit = 0;
642
        }
643
        if ($checkdigit == 10) {
644
            $checkdigit = 'X';
645
        }
646
        return (string) $checkdigit;
647
    }
648
    // }}}
649
650
    // {{{ _checkdigitISBN13()
651
     /**
652
     * Calculate checkdigit of an ISBN-13 string (Prefix + ISBN-Body)
653
     * as documented on pp.10-11 2005 handbook.
654
     *
655
     * @param string $isbnbody ISBN-Body
656
     * @param string $prefix   EAN-Prefix (Default 978 for ISBN13-978)
657
     *
658
     * @return string|false Checkdigit [0-9] or false if failed
659
     *
660
     * @access private
661
     */
662
    function _checkdigitISBN13($isbnbody, $prefix = '978')
663
    {
664
        $prefixandisbnbody = $prefix . $isbnbody;
665
666
        $t = $prefixandisbnbody;
667
        $l = strlen($t);
668
        if ($l != 12) {
669
            return false;
670
        }
671
        /* Step 1: Determine the sum of the weighted products for the first 12
672
        *  digits of the ISBN (see p.10 2005 handbook)
673
        */
674
        $ii = 1;
675
        $sum = 0;
676 View Code Duplication
        for ($i = 0; $i < 13; $i++) {
677
            $ii   = 1 - $ii;
678
            $sum += intval(substr($t, $i, 1)) * ($ii * 2 + 1);
679
        }
680
        /* Step 2: Divide the sum of the weighted products of the first 12
681
         * digits of the ISBN calculated in step 1 by 10, determining the
682
         * remainder. (see p.11 2005 handbook)
683
         */
684
        $remainder = $sum % 10;
685
686
        /* Step 3: Subtract the remainder calculated in step 2 from 10. The
687
         * resulting difference is the value of the check digit with one
688
         * exception. If the remainder from step 2 is 0, the check
689
         * digit is 0. (ebd.)
690
         */
691
        $checkdigit = 10 - $remainder;
692
        if ($remainder == 0) {
693
            $checkdigit = 0;
694
        }
695
        /* return string value */
696
        if (is_int($checkdigit)) {
697
            $checkdigit = (string) $checkdigit;
698
        }
699
        if (is_string($checkdigit) == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
700
            return false;
701
        }
702
        return $checkdigit;
703
    }
704
    // }}}
705
706
    // {{{ _isIsbnValid()
707
    /**
708
     * Validate an ISBN value
709
     *
710
     * @param string $isbn Number to validate
711
     * @param string $ver  Version to validate against
712
     *
713
     * @return integer|false    Returns the Version to signal validity or false if
714
     *                            ISBN number is not valid
715
     *
716
     * @access private
717
     */
718
    function _isIsbnValid($isbn, $ver = ISBN_DEFAULT_INPUTVERSION)
719
    {
720
        /* version handling */
721
        $r = ISBN::_isbnVersionIs($ver);
722
        if ($r === false) {
723
            return false;
724
        }
725
        if ($ver === ISBN_VERSION_UNKNOWN) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $ver (string) and ISBN_VERSION_UNKNOWN (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
726
            $ver = ISBN::_isbnVersionGuess($isbn);
727
        }
728
        if (ISBN::_isbnVersionIsValid($ver) === false) {
729
            return false;
730
        }
731
        /* since a version is available now, normalise the ISBN input */
732
        $isbnn = ISBN::_normaliseISBN($isbn);
733
        if ($isbnn === false) {
734
            return false;
735
        }
736
        /* normalzied ISBN and Version available, it's ok now
737
         * to perform indepth checks per version */
738
        switch ($ver) {
739
        case ISBN_VERSION_ISBN_10:
740
741
            /* check syntax against checkdigit */
742
            $isbnbody = ISBN::_extractISBNBody($isbnn);
743
            $check    = ISBN::_extractCheckdigit($isbnn);
744
            if ($check === false) {
745
                return false;
746
            }
747
            $checkdigit = ISBN::_checkdigitISBN10($isbnbody);
0 ignored issues
show
Security Bug introduced by
It seems like $isbnbody defined by \ISBN::_extractISBNBody($isbnn) on line 742 can also be of type false; however, ISBN::_checkdigitISBN10() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
748
            if ($checkdigit === false) {
749
                return false;
750
            }
751
            if ($checkdigit !== $check) {
752
                return false;
753
            }
754
755
            /* check registrationgroup validity */
756
            $registrationgroup = false;
757
            $subbody           = false;
758
759
            $r = ISBN::_isbnBodyParts($isbnbody, $registrationgroup, $subbody);
0 ignored issues
show
Security Bug introduced by
It seems like $isbnbody defined by \ISBN::_extractISBNBody($isbnn) on line 742 can also be of type false; however, ISBN::_isbnBodyParts() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
760
            if ($r == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
761
                return false;
762
            }
763
764
            /* check for undefined registrationgroup */
765
            if (strlen($registrationgroup) == 0) {
766
                return false;
767
            }
768
769
            /* check registrant validity */
770
            $groupid     = intval($registrationgroup);
771
            $registrant  = false;
772
            $publication = false;
773
774
            $r = ISBN::_isbnSubbodyParts($subbody, $groupid,
775
                                         $registrant, $publication);
776
            if ($r == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
777
                return false;
778
            }
779
            return true;
780
781
        case ISBN_VERSION_ISBN_13:
782
        case ISBN_VERSION_ISBN_13_978:
783
784
            /* validate EAN Prefix */
785
            $ean = ISBN::_extractEANPrefix($isbnn);
786
            if ($ean !== '978') {
787
                return false;
788
            }
789
790
            /* check syntax against checkdigit */
791
            $isbnbody = ISBN::_extractISBNBody($isbnn);
792
            $check    = ISBN::_extractCheckdigit($isbnn);
793
            if ($check === false) {
794
                return false;
795
            }
796
            $checkdigit = ISBN::_checkdigitISBN13($isbnbody);
0 ignored issues
show
Security Bug introduced by
It seems like $isbnbody defined by \ISBN::_extractISBNBody($isbnn) on line 791 can also be of type false; however, ISBN::_checkdigitISBN13() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
797
            if ($checkdigit === false) {
798
                return false;
799
            }
800
            if ($check !== $checkdigit) {
801
                return false;
802
            }
803
804
            /* validate group */
805
            $isbnbody = ISBN::_extractISBNBody($isbnn);
806
            if ($isbnbody === false) {
807
                return false;
808
            }
809
810
            $registrationgroup = false;
811
            $subbody           = false;
812
813
            $r = ISBN::_isbnBodyParts($isbnbody, $registrationgroup, $subbody);
814
            if ($r === false) {
815
                return false;
816
            }
817
818
            /* check for undefined registrationgroup */
819
            if (strlen($registrationgroup) == 0) {
820
                return false;
821
            }
822
823
            /* validate publisher */
824
            $registrant  = false;
825
            $publication = false;
826
827
            $r = ISBN::_isbnSubbodyParts($subbody, $registrationgroup,
828
                                         $registrant, $publication);
829
            if ($r === false) {
830
                return false;
831
            }
832
            return $ver;
833
834
        case ISBN_VERSION_ISBN_13_979:
835
            /* not yet standarized */
836
            return false;
837
838
        }
839
        return false;
840
    }
841
    // }}}
842
843
    // {{{ _isbnVersionGuess()
844
    /**
845
     * Guesses the version of an ISBN
846
     *
847
     * @param string $isbn ISBN Number of which the Version to guess
848
     *
849
     * @return integer|false Version Value or false (ISBN_VERSION_NONE) if failed
850
     * @access private
851
     */
852
    function _isbnVersionGuess($isbn)
853
    {
854
        $isbn = ISBN::_normaliseISBN($isbn);
855
        if ($isbn === false) {
856
            return ISBN_VERSION_NONE;
857
        }
858
        if ( strlen($isbn) == 10) {
859
            return ISBN_VERSION_ISBN_10;
860
        } else {
861
            return ISBN_VERSION_ISBN_13;
862
        }
863
    }
864
    // }}}
865
866
    // {{{ _isbnVersionIs()
867
    /**
868
     * Validate an ISBN Version value
869
     *
870
     * @param mixed $ver version to be checked being a valid ISBN Version
871
     *
872
     * @return bool true if value is valid, false if not
873
     *
874
     * @access private
875
     */
876
    function _isbnVersionIs($ver)
877
    {
878
        if (is_bool($ver) === false && is_integer($ver) === false) {
879
            return false;
880
        }
881
        switch ($ver) {
882
        case ISBN_VERSION_NONE:
883
        case ISBN_VERSION_UNKNOWN:
884
        case ISBN_VERSION_ISBN_10:
885
        case ISBN_VERSION_ISBN_13:
886
        case ISBN_VERSION_ISBN_13_978:
887
        case ISBN_VERSION_ISBN_13_979:
888
            return true;
889
890
        default:
891
            return false;
892
893
        }
894
    }
895
    // }}}
896
897
    // {{{ _isbnVersionIsValid()
898
    /**
899
     * Validate an ISBN value being a valid (identifyable -10 / -13) value
900
     *
901
     * @param mixed $ver value to be checked being a valid ISBN Version
902
     *
903
     * @return bool true if value is valid, false if not
904
     *
905
     * @access private
906
     */
907
    function _isbnVersionIsValid($ver)
908
    {
909
        $r = ISBN::_isbnVersionIs($ver);
910
        if ($r === false) {
911
            return false;
912
        }
913
914
        switch ($ver) {
915
        case ISBN_VERSION_ISBN_10:
916
        case ISBN_VERSION_ISBN_13_978:
917
            return true;
918
        default:
919
            return false;
920
        }
921
    }
922
    // }}}
923
924
    // {{{ _normaliseISBN()
925
    /**
926
     * downformat "any" ISBN Number to the very basics
927
     * an isbn number is a 10 or 13 digit. with the
928
     * 10 digit string, the last digit can be 0-9 and
929
     * X as well, all other are 0-9 only
930
     * additionally this fucntion can be used to validate
931
     * the isbn against correct length and chars
932
     *
933
     * @param string $isbn ISBN String to normalise
934
     *
935
     * @return string|false normalised ISBN Number or false if the function was
936
     *                        not able to normalise the input
937
     *
938
     * @access private
939
     */
940
    function _normaliseISBN($isbn)
941
    {
942
        /* validate input */
943
        $r = settype($isbn, 'string');
944
        if ($r === false) {
945
            return false;
946
        }
947
948
        /* normalize (trim & case)*/
949
        $isbn = trim($isbn);
950
        $isbn = strtoupper($isbn);
951
952
        /* remove lang specific prefix (if any) */
953
        $isbn = ISBN::_normaliseISBNremoveLangSpecific($isbn);
954
955
        /* remove ISBN-10: or ISBN-13: prefix (if any) */
956
        if (strlen($isbn > 8)) {
957
            $prefix = substr($isbn, 0, 8);
958
            if ($prefix == 'ISBN-10:' || $prefix == 'ISBN-13:') {
959
                $isbn = substr($isbn, 8);
960
                $isbn = ltrim($isbn);
961
            }
962
        }
963
964
        /* remove lang specific prefix again (if any) */
965
        $isbn = ISBN::_normaliseISBNremoveLangSpecific($isbn);
966
967
        /* remove "ISBN" prefix (if any)*/
968
        if (substr($isbn, 0, 4) == 'ISBN') {
969
            $isbn = substr($isbn, 4);
970
        }
971
972
        /* remove cosmetic chars and different type of spaces */
973
        $isbn = str_replace(array('-', ' ', '\t', '\n'), '', $isbn);
974
975
        /* take the length to check and differ between versions
976
         * sothat a syntaxcheck can be made */
977
        $l = strlen($isbn);
978
        if ($l != 10 && $l != 13) {
979
            return false;
980
        } elseif ($l == 10) {
981
            if (!preg_match('/^[0-9]{9}[0-9X]$/', $isbn)) {
982
                return false;
983
            }
984
        } elseif ($l == 13) {
985
            if (!ctype_digit($isbn)) {
986
                return false;
987
            }
988
        }
989
        return $isbn;
990
    }
991
    // }}}
992
993
    // {{{ _normaliseISBNremoveLangSpecific()
994
    /**
995
     * helper function for _normaliseISBN to
996
     * remove lang sepcific ISBN prefix
997
     *
998
     * @param string $isbn ISBN String to check (partially normalised)
999
     *
1000
     * @return string   input value passed through helper
1001
     *
1002
     * @access private
1003
     */
1004
    function _normaliseISBNremoveLangSpecific($isbn)
1005
    {
1006
        $lang = strtoupper(ISBN_DEFAULT_PRINT_LANG_SPECIFIC_PREFIX);
1007
        $l    = strlen($lang);
1008
        if ($l > 0 ) {
1009
            if (substr($isbn, 0, $l) == $lang) {
1010
                $isbn = substr($isbn, $l);
1011
            }
1012
        }
1013
        return $isbn;
1014
    }
1015
    // }}}
1016
1017
    // {{{ convert()
1018
    /**
1019
     * converts an ISBN number from one version to another
1020
     * can convert ISBN-10 to ISBN-13 and ISBN-13 to ISBN-10
1021
     *
1022
     * @param string  $isbnin  ISBN to convert, must be a valid ISBN Number
1023
     * @param integer $verfrom version value of the input ISBN
1024
     * @param integer $verto   version value to convert to
1025
     *
1026
     * @return string|false converted ISBN Number or false if conversion failed
1027
     */
1028
    function convert($isbnin, $verfrom = ISBN_VERSION_ISBN_10,
1029
                                   $verto = ISBN_VERSION_ISBN_13)
1030
    {
1031
        /* validate input */
1032
        if (!ISBN::_isbnVersionIsValid($verfrom)) {
1033
            return false;
1034
        }
1035
        if (!ISBN::_isbnVersionIsValid($verto)) {
1036
            return false;
1037
        }
1038
        $r = ISBN::validate($isbnin, $verfrom);
1039
        if ($r === false) {
1040
            return false;
1041
        }
1042
        /* normalize input */
1043
        $isbnn = ISBN::_normaliseISBN($isbnin);
1044
        /* input is ok now, let's convert */
1045
        switch(true) {
1046 View Code Duplication
        case $verfrom == ISBN_VERSION_ISBN_10 && $verto == ISBN_VERSION_ISBN_13:
1047
            /* convert 10 to 13 */
1048
            $isbnbody = ISBN::_extractISBNBody($isbnn);
0 ignored issues
show
Security Bug introduced by
It seems like $isbnn defined by \ISBN::_normaliseISBN($isbnin) on line 1043 can also be of type false; however, ISBN::_extractISBNBody() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1049
            if ($isbnbody === false) {
1050
                return false;
1051
            }
1052
            $isbnout = '978' . $isbnbody . ISBN::_checkdigitISBN13($isbnbody);
1053
            return $isbnout;
1054 View Code Duplication
        case $verfrom == ISBN_VERSION_ISBN_13 && $verto == ISBN_VERSION_ISBN_10:
1055
            /* convert 13 to 10 */
1056
            $isbnbody = ISBN::_extractISBNBody($isbnn);
0 ignored issues
show
Security Bug introduced by
It seems like $isbnn defined by \ISBN::_normaliseISBN($isbnin) on line 1043 can also be of type false; however, ISBN::_extractISBNBody() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1057
            if ($isbnbody === false) {
1058
                return false;
1059
            }
1060
            $isbnout = $isbnbody . ISBN::_checkdigitISBN10($isbnbody);
1061
            return $isbnout;
1062
        case $verfrom == $verto:
1063
            /* version is the same so there is no need to convert */
1064
            /* hej, praktisch! */
1065
            return $isbnn;
1066
        }
1067
        return false;
1068
    }
1069
    // }}}
1070
1071
    // {{{ getCheckdigit()
1072
    /**
1073
     * Get the Checkdigit Part of ISBN Number
1074
     *
1075
     * @return string|false Checkdigit or false if failed
1076
     */
1077
    function getCheckdigit()
1078
    {
1079
        $ver   = $this->getVersion();
1080
        $check = false;
1081
1082
        switch ($ver) {
1083
        case ISBN_VERSION_ISBN_10:
1084
            $check = ISBN::_checkdigitISBN10($this->_getISBNBody());
1085
            break;
1086
1087
        case ISBN_VERSION_ISBN_13:
1088
            $check = ISBN::_checkdigitISBN13($this->_getISBNBody());
1089
            break;
1090
1091
        }
1092
1093
        return $check;
1094
    }
1095
    // }}}
1096
1097
    // {{{ getEAN()
1098
    /**
1099
     * Get the EAN Prefix of ISBN Number (ISBN-13)
1100
     *
1101
     * @return string|false EAN Prefix or false if failed
1102
     */
1103
    function getEAN()
1104
    {
1105
        $ver = $this->getVersion();
1106
        if ($ver === false ) {
1107
            return false;
1108
        }
1109
        if ($ver == ISBN_VERSION_ISBN_13_978) {
1110
            return '978';
1111
        }
1112
        if ($ver == ISBN_VERSION_ISBN_13_979) {
1113
            return '979';
1114
        }
1115
        return '';
1116
    }
1117
    // }}}
1118
1119
    // {{{ getGroup()
1120
    /**
1121
     * Get the Registrationgroup Part of the ISBN Number
1122
     *
1123
     * @return string|false Group Identifier or false if failed
1124
     */
1125
    function getGroup()
1126
    {
1127
        return $this->isbn_group;
1128
    }
1129
    // }}}
1130
1131
    // {{{ _setGroup()
1132
    /**
1133
     * Setter for the Registrationgroup Part of the ISBN Number
1134
     *
1135
     * @param string $group Registrationsgroup to set
1136
     *
1137
     * @return void
1138
     *
1139
     * @throws ISBN_Exception in case it fails
1140
     */
1141
    function _setGroup($group)
1142
    {
1143
        if (is_string($group) == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1144
            return new ISBN_Exception('Wrong Vartype');
1145
        }
1146
        $l = strlen($group);
1147
        if ($l < 1 || $l > 5) {
1148
            return new ISBN_Exception('Wrong Group Length (' . $l . ')');
1149
        }
1150
        $testbody  = substr($group . '000000000', 0, 9);
1151
        $testgroup = ISBN::_extractGroup($testbody);
1152
        if ($testgroup === false ) {
1153
            return new ISBN_Exception('Invalid Group');
1154
        }
1155
        if ($testgroup != $group) {
1156
            return new ISBN_Exception('Invalid Group');
1157
        }
1158
        $this->isbn_group = $group;
1159
    }
1160
1161
    // {{{ getISBN()
1162
    /**
1163
     * Get whole ISBN Number
1164
     *
1165
     * @return string ISBN Number (unformatted); empty string if this is
1166
     *                not a valid ISBN
1167
     */
1168
    function getISBN()
1169
    {
1170
        $ver = $this->getVersion();
1171
        if ($ver === false ) {
1172
            return '';
1173
        }
1174
1175
        $isbn = '';
1176
1177
        $r = ISBN::_isbnVersionIsValid($ver);
1178
        if ($r === false ) {
1179
            return $isbn;
1180
        }
1181
1182
        $isbn .= $this->getEAN();
1183
        $isbn .= $this->_getISBNBody();
1184
        $isbn .= $this->getCheckdigit();
1185
1186
        return $isbn;
1187
    }
1188
    // }}}
1189
1190
    // {{{ getISBNDisplayable()
1191
    /**
1192
     * Get whole ISBN Number in a displayable fashion (see Handbook p. 15)
1193
     *
1194
     * @param string $format Formatstring 1-4 Chars:
1195
     *               each character is a control char:
1196
     *               #1 i or not: use international pre-prefix
1197
     *               #2 i or not: "ISBN" in front or v: incl. version
1198
     *               #3 : or not: insert a ":"
1199
     *               #4 - or not: use - after EAN (ISBN 13 only)
1200
     *               #4 or =: use - between each ISBN part
1201
     *               Example 1:
1202
     *                  '   --' 978-0-385-33941-4
1203
     *                  classic displayable ISBN
1204
     *               Example 2:
1205
     *                  ' v:-' ISBN-13: 978-0385339414
1206
     *                  ISBN-Format used by amazon
1207
     *               Example 3:
1208
     *                  'iv:=' ISBN-13: 978-0-385-33941-4
1209
     *                  full blown: more is more!
1210
     *
1211
     * @return string ISBN Number (formatted); empty string if this is
1212
     *                not a valid ISBN
1213
     */
1214
    function getISBNDisplayable($format = '')
1215
    {
1216
        if ( strlen($format)==0 ) {
1217
            $format = 'iv:='; //edit $this->ISBNFormatstring;
1218
        }
1219
        $format = substr($format . '    ', 0, 4);
1220
1221
        $ver = $this->getVersion();
1222
        if ($ver === false ) {
1223
            return '';
1224
        }
1225
1226
        $isbn = '';
1227
1228
        $r = ISBN::_isbnVersionIsValid($ver);
1229
        if ($r === false ) {
1230
            return $isbn;
1231
        }
1232
1233
        if ($format[0] == 'i') {
1234
            $isbn .= ISBN_DEFAULT_PRINT_LANG_SPECIFIC_PREFIX;
1235
            if (strlen($isbn)) $isbn .= ' ';
1236
        }
1237
1238
        if ($format[1] == 'i' || $format[1] == 'v') {
1239
            $isbn .= 'ISBN';
1240
            if ($format[1] == 'v') {
1241
                switch ($ver) {
1242
                case ISBN_VERSION_ISBN_10:
1243
                    $isbn .= '-10';
1244
                    break;
1245
                case ISBN_VERSION_ISBN_13:
1246
                    $isbn .= '-13';
1247
                    break;
1248
                }
1249
            }
1250
        }
1251
1252
        if ($format[2] == ':') {
1253
            $isbn .= ':';
1254
        }
1255
1256
        if (strlen($isbn)) {
1257
            $isbn .= ' ';
1258
        }
1259
1260
        if ($ver == ISBN_VERSION_ISBN_13_978 || $ver == ISBN_VERSION_ISBN_13_979) {
1261
            $isbn .= $this->getEAN();
1262
            if ($format[3] == '-' || $format[3] == '=') {
1263
                $isbn .= ISBN_DEFAULT_COSMETIC_SEPERATOR;
1264
            }
1265
        }
1266
1267
        $seperator = ($format[3] == '=') ? ISBN_DEFAULT_COSMETIC_SEPERATOR : '';
1268
1269
        $isbn .= $this->getGroup() . $seperator;
1270
        $isbn .= $this->getPublisher() . $seperator;
1271
        $isbn .= $this->getTitle() . $seperator;
1272
        $isbn .= $this->getCheckdigit();
1273
1274
        return $isbn;
1275
1276
    }
1277
    // }}}
1278
1279
    // {{{ setISBN()
1280
    /**
1281
     * Setter for ISBN
1282
     *
1283
     * @param string $isbn ISBN Number
1284
     *          this is a valid ISBN Number or it is an Empty string
1285
     *          which will reset the class
1286
     *
1287
     * @return void
1288
     *
1289
     * @throws ISBN_Exception in case it fails
1290
     *
1291
     */
1292
    function setISBN($isbn)
1293
    {
1294
        if ($isbn == '') {
1295
            $this->ver            = ISBN_VERSION_NONE;
1296
            $this->isbn_group     = '';
1297
            $this->isbn_publisher = '';
1298
            $this->isbn_title     = '';
1299
        } else {
1300
            $isbnn = ISBN::_normaliseISBN($isbn);
1301
            $ver   = ISBN::_getVersion($isbnn);
0 ignored issues
show
Security Bug introduced by
It seems like $isbnn defined by \ISBN::_normaliseISBN($isbn) on line 1300 can also be of type false; however, ISBN::_getVersion() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1302
            if ($ver === false) {
1303
                return new ISBN_Exception('Invalid ISBN');
1304
            }
1305
            if ($ver != $this->ver && $this->ver !== ISBN_VERSION_NONE) {
1306
                return new ISBN_Exception(
1307
                  'ISBN Version of passed ISBN (' . $ver . ') '.
1308
                  'does not match existing (' . $this->ver . ').'
1309
                );
1310
            } elseif ($this->ver === ISBN_VERSION_NONE) {
1311
                $this->ver = $ver;
1312
            }
1313
            $body = ISBN::_extractISBNBody($isbnn);
0 ignored issues
show
Security Bug introduced by
It seems like $isbnn defined by \ISBN::_normaliseISBN($isbn) on line 1300 can also be of type false; however, ISBN::_extractISBNBody() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1314
            if ($body === false) {
1315
                return new ISBN_Exception('Invalid ISBN (could not extract body)');
1316
            }
1317
            $e =  $this->_setISBNBody($body);
1318
            if ("ISBN_Exception" == get_class($e)) {
1319
                return new ISBN_Exception(
1320
                    'Invalid ISBN (invalid body "' . $body . '")', $e
1321
                );
1322
            }
1323
        }
1324
    }
1325
    // }}}
1326
1327
    // {{{ _getISBNBody()
1328
    /**
1329
     * _getISBNBody()
1330
     *
1331
     * @return string ISBN Body (not an offical term)
1332
     */
1333
    function _getISBNBody()
1334
    {
1335
        $body  = '';
1336
        $body .= $this->getGroup();
1337
        $body .= $this->_getISBNSubbody();
1338
        return $body;
1339
    }
1340
    // }}}
1341
1342
    // {{{ _setISBNBody()
1343
    /**
1344
     * _setISBNBody()
1345
     *
1346
     * Setter for ISBNBody
1347
     *
1348
     * @param string $body ISBNBody
1349
     *
1350
     * @return void
1351
     *
1352
     * @throws ISBN_Exception in case it fails
1353
     */
1354
    function _setISBNBody($body)
1355
    {
1356
        /* validate parameter */
1357
        if (is_string($body) == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1358
            return new ISBN_Exception('Not a Body: wrong variabletype');
1359
        }
1360
        if (strlen($body) != 9) {
1361
            return new ISBN_Exception('Not a Body: wrong body length');
1362
        }
1363
        if (ctype_digit($body) !== true) {
1364
            return new ISBN_Exception('Not a Body: syntactically not a body');
1365
        }
1366
1367
        /* validate body by extracting and validating parts */
1368
        $group   = false;
1369
        $subbody = false;
1370
1371
        $r = ISBN::_isbnBodyParts($body, $group, $subbody);
1372
        if ($r == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1373
            return new ISBN_Exception('Invalid Body');
1374
        }
1375
1376
        $e =  $this->_setGroup($group);
1377
        if ("ISBN_Exception" == get_class($e)) {
1378
            return new ISBN_Exception('Invalid Body: Group is invalid', $e);
1379
        }
1380
1381
        $e = $this->_setISBNSubbody($subbody);
1382
        if ("ISBN_Exception" == get_class($e)) {
1383
            return new ISBN_Exception(
1384
                'Invalid Body: Subbody is invalid (' . $e->getMessage() . ')', $e
1385
            );
1386
        }
1387
    }
1388
    // }}}
1389
1390
    // {{{ _getISBNSubbody()
1391
    /**
1392
     * Get ISBNSubbody ()
1393
     *
1394
     * @return ISBN Subbody
1395
     */
1396
    function _getISBNSubbody()
1397
    {
1398
        $subbody  = '';
1399
        $subbody .= $this->getPublisher();
1400
        $subbody .= $this->getTitle();
1401
        return $subbody;
1402
    }
1403
    // }}}
1404
1405
    // {{{ _setISBNSubbody()
1406
    /**
1407
     * _setISBNSubbody
1408
     *
1409
     * Setter for the ISBN Subbody
1410
     *
1411
     * @param string $subbody ISBN Subbody
1412
     *
1413
     * @return void
1414
     *
1415
     * @throws ISBN_Exception in case it fails
1416
     */
1417
    function _setISBNSubbody($subbody)
1418
    {
1419
        /* validate parameter */
1420
        if (is_string($subbody) == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1421
            return new ISBN_Exception('Wrong Vartype');
1422
        }
1423
        $l = strlen($subbody);
1424
        if ( $l < 4 || $l > 8) {
1425
            return new ISBN_Exception('Not a Subbody by length');
1426
        }
1427
        /* validate by setting apart */
1428
        $registrant  = false;
1429
        $publication = false;
1430
        $groupid     = intval($this->isbn_group);
1431
1432
        $r = ISBN::_isbnSubbodyParts($subbody, $groupid, $registrant, $publication);
1433
        if ($r === false) {
1434
            return new ISBN_Exception('Can\'t break');
1435
        }
1436
        /* edit+ setter/getter for Registrant/Publisher and Title/Publication */
1437
        $this->isbn_publisher = $registrant;
1438
        $this->isbn_title     = $publication;
1439
    }
1440
1441
    // {{{ getPublisher()
1442
    /**
1443
     * Get the Publication Part of the ISBN Number
1444
     *
1445
     * @return string|false Publisher or false if failed
1446
     */
1447
    function getPublisher()
1448
    {
1449
        return $this->isbn_publisher;
1450
    }
1451
    // }}}
1452
1453
    // {{{ getTitle()
1454
    /**
1455
     * Get the Title Part of the ISBN Number
1456
     *
1457
     * @return string|false Title or false if failed
1458
     */
1459
    function getTitle()
1460
    {
1461
        return $this->isbn_title;
1462
    }
1463
    // }}}
1464
1465
1466
    // {{{ isValid()
1467
    /**
1468
     * Returns this ISBN validity
1469
     *
1470
     * @return boolean
1471
     */
1472
    function isValid()
1473
    {
1474
        $isbn = $this->getISBN();
0 ignored issues
show
Unused Code introduced by
$isbn is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1475
        $r    = ISBN::validate($this->getISBN(), $this->getVersion());
1476
        return (bool) $r;
1477
    }
1478
1479
    // {{{ validate()
1480
    /**
1481
     * Validates an ISBN
1482
     *
1483
     * @param string  $isbn ISBN to validate
1484
     * @param integer $ver  ISBN-Version to validate against
1485
     *
1486
     * @return integer|false    Version value of a valid ISBN or false
1487
     *                          if it did not validate
1488
     */
1489
    function validate($isbn, $ver = ISBN_DEFAULT_INPUTVERSION)
1490
    {
1491
        $r = ISBN::_isbnVersionIs($ver);
1492
        if ($r === false) {
1493
            return false;
1494
        }
1495
        if ($ver === ISBN_VERSION_UNKNOWN) {
1496
            $ver = ISBN::_isbnVersionGuess($isbn);
1497
        }
1498
        if (ISBN::_isbnVersionIsValid($ver) === false) {
1499
            return false;
1500
        }
1501
        $r = ISBN::_isIsbnValid($isbn, $ver);
1502
        if ($r === false) {
1503
            return false;
1504
        }
1505
        return $ver;
1506
    }
1507
    // }}}
1508
1509
    // {{{ getVersion()
1510
    /**
1511
     * Returns version of this objects ISBN
1512
     *
1513
     * @return integer|false  Version value or ISBN_VERSION_NONE
1514
     */
1515
    function getVersion()
1516
    {
1517
        return $this->ver;
1518
    }
1519
1520
1521
    // {{{ guessVersion()
1522
    /**
1523
     * Guesses ISBN version of passed string
1524
     *
1525
     * Note: This is not Validation. To get the validated
1526
     * version of an ISBN Number use ISBN::validate();
1527
     *
1528
     * @param string $isbn ISBN Number to guess Version of
1529
     *
1530
     * @return integer|false    Version Value or false if failed
1531
     *
1532
     * @see validate();
1533
     */
1534
    function guessVersion($isbn)
1535
    {
1536
        $r = ISBN::_isbnVersionGuess($isbn);
1537
        return $r;
1538
    }
1539
    // }}}
1540
1541
}
1542