/
Id3v2.php
713 lines (656 loc) · 25.3 KB
/
Id3v2.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@zend.com so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Media
* @subpackage ID3
* @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @version $Id$
*/
/**#@+ @ignore */
require_once 'Zend/Media/Id3/Object.php';
require_once 'Zend/Media/Id3/Header.php';
/**#@-*/
/**
* This class represents a file containing ID3v2 header as described in
* {@link http://www.id3.org/id3v2.4.0-structure ID3v2 structure document}.
*
* ID3v2 is a general tagging format for audio, which makes it possible to store
* meta data about the audio inside the audio file itself. The ID3 tag is mainly
* targeted at files encoded with MPEG-1/2 layer I, MPEG-1/2 layer II, MPEG-1/2
* layer III and MPEG-2.5, but may work with other types of encoded audio or as
* a stand alone format for audio meta data.
*
* ID3v2 is designed to be as flexible and expandable as possible to meet new
* meta information needs that might arise. To achieve that ID3v2 is constructed
* as a container for several information blocks, called frames, whose format
* need not be known to the software that encounters them. Each frame has an
* unique and predefined identifier which allows software to skip unknown
* frames.
*
* @category Zend
* @package Zend_Media
* @subpackage ID3
* @author Sven Vollbehr <sven@vollbehr.eu>
* @author Ryan Butterfield <buttza@gmail.com>
* @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @version $Id$
*/
final class Zend_Media_Id3v2 extends Zend_Media_Id3_Object
{
/** @var Zend_Media_Id3_Header */
private $_header;
/** @var Zend_Media_Id3_ExtendedHeader */
private $_extendedHeader;
/** @var Zend_Media_Id3_Header */
private $_footer;
/** @var Array */
private $_frames = array();
/** @var string */
private $_filename = null;
/**
* Constructs the Zend_Media_Id3v2 class with given file and options. The
* options array may also be given as the only parameter.
*
* The following options are currently recognized:
* o encoding -- Indicates the encoding that all the texts are presented
* with. See the documentation of iconv for supported values. Please
* note that write operations do not convert string and thus encodings
* are limited to those supported by the {@link Zend_Media_Id3_Encoding}
* interface.
* o version -- The ID3v2 tag version to use in write operation. This
* option is automatically set when a tag is read from a file and
* defaults to version 4.0 for tag write.
* o compat -- Normally unsynchronization is handled automatically behind
* the scenes. However, current versions of Windows operating system and
* Windows Media Player, just to name a few, do not support ID3v2.4 tags
* nor ID3v2.3 tags with unsynchronization. Hence, for compatibility
* reasons, this option is made available to disable automatic tag level
* unsynchronization scheme that version 3.0 supports.
* o readonly -- Indicates that the tag is read from a temporary file or
* another source it cannot be written back to. The tag can, however,
* still be written to another file.
*
* @todo Only limited subset of flags are processed.
* @todo Utilize the SEEK frame and search for a footer to find the tag
* @todo Utilize the LINK frame to fetch frames from other sources
* @param string|resource|Zend_Io_Reader $filename The path to the file,
* file descriptor of an opened file, or a {@link Zend_Io_Reader} instance.
* @param Array $options The options array.
* @throws Zend_Media_Id3_Exception if given file descriptor is not valid
*/
public function __construct($filename = null, $options = array())
{
parent::__construct(null, $options);
if (is_array($filename)) {
$options = $filename;
$filename = null;
}
if ($filename === null) {
$this->_header = new Zend_Media_Id3_Header(null, $options);
return;
}
if ($filename instanceof Zend_Io_Reader) {
$this->_reader = &$filename;
} else {
require_once 'Zend/Io/FileReader.php';
try {
$this->_reader = new Zend_Io_FileReader($filename);
} catch (Zend_Io_Exception $e) {
$this->_reader = null;
require_once 'Zend/Media/Id3/Exception.php';
throw new Zend_Media_Id3_Exception($e->getMessage());
}
if (is_string($filename) && !isset($options['readonly'])) {
$this->_filename = $filename;
}
}
$startOffset = $this->_reader->getOffset();
if ($this->_reader->read(3) != 'ID3') {
require_once 'Zend/Media/Id3/Exception.php';
throw new Zend_Media_Id3_Exception
('File does not contain ID3v2 tag');
}
$this->_header = new Zend_Media_Id3_Header($this->_reader, $options);
$tagSize = $this->_header->getSize();
if ($this->_header->getVersion() < 3 ||
$this->_header->getVersion() > 4) {
require_once 'Zend/Media/Id3/Exception.php';
throw new Zend_Media_Id3_Exception
('File does not contain ID3v2 tag of supported version: v2.' .
$this->_header->getVersion());
}
if ($this->_header->getVersion() < 4 &&
$this->_header->hasFlag(Zend_Media_Id3_Header::UNSYNCHRONISATION)) {
$data = $this->_reader->read($this->_header->getSize());
require_once 'Zend/Io/StringReader.php';
$this->_reader = new Zend_Io_StringReader
($this->_decodeUnsynchronisation($data));
$tagSize = $this->_reader->getSize();
}
$this->clearOption('unsynchronisation');
if ($this->_header->hasFlag(Zend_Media_Id3_Header::UNSYNCHRONISATION)) {
$this->setOption('unsynchronisation', true);
}
if ($this->_header->hasFlag(Zend_Media_Id3_Header::EXTENDED_HEADER)) {
require_once 'Zend/Media/Id3/ExtendedHeader.php';
$this->_extendedHeader =
new Zend_Media_Id3_ExtendedHeader($this->_reader, $options);
}
if ($this->_header->hasFlag(Zend_Media_Id3_Header::FOOTER)) {
// skip footer, and rather copy header
$this->_footer = &$this->_header;
}
while (true) {
$offset = $this->_reader->getOffset();
// Jump off the loop if we reached the end of the tag
if ($offset - $startOffset - 10 >= $tagSize -
($this->hasFooter() ? 10 : 0) - 10 /* header */) {
break;
}
// Jump off the loop if we reached padding
if (ord($identifier = $this->_reader->read(1)) === 0) {
break;
}
$identifier .= $this->_reader->read(3);
// Jump off the loop if we reached invalid entities. This fix is
// just to make things work. Utility called MP3ext does not seem
// to know what it is doing as it uses padding to write its
// version information there.
if ($identifier == 'MP3e') {
break;
}
$this->_reader->setOffset($offset);
if (@fopen($file = 'Zend/Media/Id3/Frame/' .
ucfirst(strtolower($identifier)) . '.php', 'r',
true) !== false) {
require_once($file);
}
if (class_exists
($classname = 'Zend_Media_Id3_Frame_' .
ucfirst(strtolower($identifier)))) {
$frame = new $classname($this->_reader, $options);
} else {
require_once 'Zend/Media/Id3/Frame/Unknown.php';
$frame =
new Zend_Media_Id3_Frame_Unknown($this->_reader, $options);
}
if (!isset($this->_frames[$frame->getIdentifier()])) {
$this->_frames[$frame->getIdentifier()] = array();
}
$this->_frames[$frame->getIdentifier()][] = $frame;
}
}
/**
* Returns the header object.
*
* @return Zend_Media_Id3_Header
*/
public function getHeader()
{
return $this->_header;
}
/**
* Checks whether there is an extended header present in the tag. Returns
* <var>true</var> if the header is present, <var>false</var> otherwise.
*
* @return boolean
*/
public function hasExtendedHeader()
{
if ($this->_header) {
return $this->_header->hasFlag
(Zend_Media_Id3_Header::EXTENDED_HEADER);
}
return false;
}
/**
* Returns the extended header object if present, or <var>false</var>
* otherwise.
*
* @return Zend_Media_Id3_ExtendedHeader|false
*/
public function getExtendedHeader()
{
if ($this->hasExtendedHeader()) {
return $this->_extendedHeader;
}
return false;
}
/**
* Sets the extended header object.
*
* @param Zend_Media_Id3_ExtendedHeader $extendedHeader The header object
*/
public function setExtendedHeader($extendedHeader)
{
if (is_subclass_of($extendedHeader, 'Zend_Media_Id3_ExtendedHeader')) {
$this->_header->flags =
$this->_header->flags | Zend_Media_Id3_Header::EXTENDED_HEADER;
$this->_extendedHeader->setOptions($this->getOptions());
$this->_extendedHeader = $extendedHeader;
} else {
require_once 'Zend/Media/Id3/Exception.php';
throw new Zend_Media_Id3_Exception('Invalid argument');
}
}
/**
* Checks whether there is a frame given as an argument defined in the tag.
* Returns <var>true</var> if one ore more frames are present,
* <var>false</var> otherwise.
*
* @param string $identifier The frame name.
* @return boolean
*/
public function hasFrame($identifier)
{
return isset($this->_frames[$identifier]);
}
/**
* Returns all the frames the tag contains as an associate array. The frame
* identifiers work as keys having an array of frames as associated value.
*
* @return Array
*/
public function getFrames()
{
return $this->_frames;
}
/**
* Returns an array of frames matching the given identifier or an empty
* array if no frames matched the identifier.
*
* The identifier may contain wildcard characters '*' and '?'. The asterisk
* matches against zero or more characters, and the question mark matches
* any single character.
*
* Please note that one may also use the shorthand $obj->identifier to
* access the first frame with the identifier given. Wildcards cannot be
* used with the shorthand method.
*
* @param string $identifier The frame name.
* @return Array
*/
public function getFramesByIdentifier($identifier)
{
$matches = array();
$searchPattern = '/^' .
str_replace(array('*', '?'), array('.*', '.'), $identifier) . '$/i';
foreach ($this->_frames as $identifier => $frames) {
if (preg_match($searchPattern, $identifier)) {
foreach ($frames as $frame) {
$matches[] = $frame;
}
}
}
return $matches;
}
/**
* Removes any frames matching the given object identifier.
*
* The identifier may contain wildcard characters '*' and '?'. The asterisk
* matches against zero or more characters, and the question mark matches
* any single character.
*
* One may also use the shorthand unset($obj->identifier) to achieve the
* same result. Wildcards cannot be used with the shorthand method.
*
* @param string $identifier The frame name.
*/
public final function removeFramesByIdentifier($identifier)
{
$searchPattern = '/^' .
str_replace(array('*', '?'), array('.*', '.'), $identifier) . '$/i';
foreach ($this->_frames as $identifier => $frames) {
if (preg_match($searchPattern, $identifier)) {
foreach ($frames as $key => $value) {
unset($this->_frames[$identifier][$key]);
}
}
}
}
/**
* Adds a new frame to the tag and returns it.
*
* @param Zend_Media_Id3_Frame $frame The frame to add.
* @return Zend_Media_Id3_Frame
*/
public function addFrame($frame)
{
$frame->setOptions($this->getOptions());
if (!$this->hasFrame($frame->getIdentifier())) {
$this->_frames[$frame->getIdentifier()] = array();
}
return $this->_frames[$frame->getIdentifier()][] = $frame;
}
/**
* Remove the given frame from the tag.
*
* @param Zend_Media_Id3_Frame $frame The frame to remove.
*/
public function removeFrame($frame)
{
if (!$this->hasFrame($frame->getIdentifier())) {
return;
}
foreach ($this->_frames[$frame->getIdentifier()] as $key => $value) {
if ($frame === $value) {
unset($this->_frames[$frame->getIdentifier()][$key]);
}
}
}
/**
* Checks whether there is a footer present in the tag. Returns
* <var>true</var> if the footer is present, <var>false</var> otherwise.
*
* @return boolean
*/
public function hasFooter()
{
return $this->_header->hasFlag(Zend_Media_Id3_Header::FOOTER);
}
/**
* Returns the footer object if present, or <var>false</var> otherwise.
*
* @return Zend_Media_Id3_Header|false
*/
public function getFooter()
{
if ($this->hasFooter()) {
return $this->_footer;
}
return false;
}
/**
* Sets whether the tag should have a footer defined.
*
* @param boolean $useFooter Whether the tag should have a footer
*/
public function setFooter($useFooter)
{
if ($useFooter) {
$this->_header->setFlags
($this->_header->getFlags() | Zend_Media_Id3_Header::FOOTER);
$this->_footer = &$this->_header;
} else {
/* Count footer bytes towards the tag size, so it gets removed or
overridden upon re-write */
if ($this->hasFooter()) {
$this->_header->setSize($this->_header->getSize() + 10);
}
$this->_header->setFlags
($this->_header->getFlags() & ~Zend_Media_Id3_Header::FOOTER);
$this->_footer = null;
}
}
/**
* Writes the possibly altered ID3v2 tag back to the file where it was read.
* If the class was constructed without a file name, one can be provided
* here as an argument. Regardless, the write operation will override
* previous tag information, if found.
*
* If write is called on a tag without any frames to it, current tag is
* removed from the file altogether.
*
* @param string|Zend_Io_Writer $filename The optional path to the file, use
* null to save to the same file.
*/
public function write($filename)
{
if ($filename === null && ($filename = $this->_filename) === null) {
require_once 'Zend/Media/Id3/Exception.php';
throw new Zend_Media_Id3_Exception('No file given to write to');
} else if ($filename !== null && $filename instanceof Zend_Io_Writer) {
require_once 'Zend/Io/Writer.php';
$this->_writeData($filename);
return;
} else if ($filename !== null && $this->_filename !== null &&
realpath($filename) != realpath($this->_filename) &&
!copy($this->_filename, $filename)) {
require_once 'Zend/Media/Id3/Exception.php';
throw new Zend_Media_Id3_Exception
('Unable to copy source to destination: ' .
realpath($this->_filename) . '->' . realpath($filename));
}
if (($fd = fopen
($filename, file_exists($filename) ? 'r+b' : 'wb')) === false) {
require_once 'Zend/Media/Id3/Exception.php';
throw new Zend_Media_Id3_Exception
('Unable to open file for writing: ' . $filename);
}
$hasNoFrames = true;
foreach ($this->_frames as $identifier => $instances) {
if (count($instances) > 0) {
$hasNoFrames = false;
break;
}
}
if ($hasNoFrames === true) {
$this->remove(new Zend_Io_Reader($fd));
return;
}
if ($this->_reader !== null) {
$oldTagSize = 10 /* header */ + $this->_header->getSize();
} else {
$reader = new Zend_Io_Reader($fd);
if ($reader->read(3) == 'ID3') {
$header = new Zend_Media_Id3_Header($reader);
$oldTagSize = 10 /* header */ + $header->getSize();
} else {
$oldTagSize = 0;
}
}
require_once 'Zend/Io/StringWriter.php';
$tag = new Zend_Io_StringWriter();
$this->_writeData($tag);
$tagSize = $tag->getSize();
if ($tagSize > $oldTagSize) {
fseek($fd, 0, SEEK_END);
$oldFileSize = ftell($fd);
ftruncate
($fd, $newFileSize = $tagSize - $oldTagSize + $oldFileSize);
for ($i = 1, $cur = $oldFileSize; $cur > 0; $cur -= 1024, $i++) {
if ($cur >= 1024) {
fseek($fd, -(($i * 1024) +
($newFileSize - $oldFileSize)), SEEK_END);
$buffer = fread($fd, 1024);
fseek($fd, -($i * 1024), SEEK_END);
$bytes = fwrite($fd, $buffer, 1024);
} else {
fseek($fd, 0);
$buffer = fread($fd, $cur);
fseek($fd, $newFileSize - $oldFileSize);
$bytes = fwrite($fd, $buffer, $cur);
}
}
if (($remaining = $oldFileSize % 1024) != 0) {
// huh?
}
fseek($fd, 0, SEEK_END);
}
fseek($fd, 0);
for ($i = 0; $i < $tagSize; $i += 1024) {
fseek($tag->getFileDescriptor(), $i);
$bytes = fwrite($fd, fread($tag->getFileDescriptor(), 1024));
}
fclose($fd);
$this->_filename = $filename;
}
/**
* Writes the tag data.
*
* @param Zend_Io_Writer $writer The writer object.
* @return void
*/
private function _writeData($writer)
{
$this->clearOption('unsynchronisation');
$buffer = new Zend_Io_StringWriter();
foreach ($this->_frames as $frames) {
foreach ($frames as $frame) {
$frame->write($buffer);
}
}
$frameData = $buffer->toString();
$frameDataLength = strlen($frameData);
$paddingLength = 0;
// ID3v2.4.0 supports frame level unsynchronisation while
// ID3v2.3.0 supports only tag level unsynchronisation.
if ($this->getOption('version', 4) < 4 &&
$this->getOption('compat', false) !== true) {
$frameData = $this->_encodeUnsynchronisation($frameData);
if (($len = strlen($frameData)) != $frameDataLength) {
$frameDataLength = $len;
$this->_header->setFlags
($this->_header->getFlags() |
Zend_Media_Id3_Header::UNSYNCHRONISATION);
} else {
$this->_header->setFlags
($this->_header->getFlags() &
~Zend_Media_Id3_Header::UNSYNCHRONISATION);
}
}
// The tag padding is calculated as follows. If the tag can be written
// in the space of the previous tag, the remaining space is used for
// padding. If there is no previous tag or the new tag is bigger than
// the space taken by the previous tag, the padding is a constant
// 4096 bytes.
if ($this->hasFooter() === false) {
if ($this->_reader !== null &&
$frameDataLength < $this->_header->getSize()) {
$paddingLength = $this->_header->getSize() - $frameDataLength;
} else {
$paddingLength = 4096;
}
}
/* ID3v2.4.0 CRC calculated w/ padding */
if ($this->getOption('version', 4) >= 4) {
$frameData =
str_pad($frameData, $frameDataLength += $paddingLength, "\0");
}
$extendedHeaderData = '';
$extendedHeaderDataLength = 0;
if ($this->hasExtendedHeader()) {
$this->_extendedHeader->setPadding($paddingLength);
if ($this->_extendedHeader->hasFlag
(Zend_Media_Id3_ExtendedHeader::CRC32)) {
$crc = crc32($frameData);
if ($crc & 0x80000000) {
$crc = -(($crc ^ 0xffffffff) + 1);
}
$this->_extendedHeader->setCrc($crc);
}
$buffer = new Zend_Io_StringWriter();
$this->_extendedHeader->write($buffer);
$extendedHeaderData = $buffer->toString();
$extendedHeaderDataLength = strlen($extendedHeaderData);
}
/* ID3v2.3.0 CRC calculated w/o padding */
if ($this->getOption('version', 4) < 4) {
$frameData =
str_pad($frameData, $frameDataLength += $paddingLength, "\0");
}
$this->_header->setSize($extendedHeaderDataLength + $frameDataLength);
$writer->write('ID3');
$this->_header->write($writer);
$writer->write($extendedHeaderData);
$writer->write($frameData);
if ($this->hasFooter()) {
$writer->write('3DI');
$this->_footer->write($writer);
}
}
/**
* Removes the ID3v2 tag altogether.
*
* @param string $filename The path to the file.
*/
public static function remove($filename)
{
if ($filename instanceof Zend_Io_Reader) {
$reader = &$filename;
} else {
require_once 'Zend/Io/FileReader.php';
$reader = new Zend_Io_FileReader($filename, 'r+b');
}
$fileSize = $reader->getSize();
if ($reader->read(3) == 'ID3') {
$header = new Zend_Media_Id3_Header($reader);
$tagSize = 10 /* header */ + $header->getSize();
} else return;
$fd = $reader->getFileDescriptor();
for ($i = 0; $tagSize + ($i * 1024) < $fileSize; $i++) {
fseek($fd, $tagSize + ($i * 1024));
$buffer = fread($fd, 1024);
fseek($fd, ($i * 1024));
$bytes = fwrite($fd, $buffer, 1024);
}
ftruncate($fd, $fileSize - $tagSize);
}
/**
* Magic function so that $obj->value will work. The method will attempt to
* return the first frame that matches the identifier.
*
* If there is no frame or field with given name, the method will attempt to
* create a frame with given identifier.
*
* If none of these work, an exception is thrown.
*
* @param string $name The frame or field name.
* @return mixed
*/
public function __get($name)
{
if (isset($this->_frames[strtoupper($name)])) {
return $this->_frames[strtoupper($name)][0];
}
if (method_exists($this, 'get' . ucfirst($name))) {
return call_user_func
(array($this, 'get' . ucfirst($name)));
}
if (@fopen($filename = 'Zend/Media/Id3/Frame/' . ucfirst($name) .
'.php', 'r', true) !== false) {
require_once $filename;
}
if (class_exists
($classname = 'Zend_Media_Id3_Frame_' . ucfirst($name))) {
return $this->addFrame(new $classname());
}
require_once 'Zend/Media/Id3/Exception.php';
throw new Zend_Media_Id3_Exception('Unknown frame/field: ' . $name);
}
/**
* Magic function so that isset($obj->value) will work. This method checks
* whether the frame matching the identifier exists.
*
* @param string $name The frame identifier.
* @return boolean
*/
public function __isset($name)
{
return isset($this->_frames[strtoupper($name)]);
}
/**
* Magic function so that unset($obj->value) will work. This method removes
* all the frames matching the identifier.
*
* @param string $name The frame identifier.
*/
public function __unset($name)
{
unset($this->_frames[strtoupper($name)]);
}
}