Issues

ZF-11947: Zend_GData does not handle major protocol version 2 correctly when building dom tree

Description

When processing a YouTube response requested with major version 2, some nodes that are clearly present in the XML stream, are not found. Example is entry->getControl() would return null, even though the element is present. I traced this back to an oversight in the following code:


    protected function takeChildFromDOM($child)
    {
        $absoluteNodeName = $child->namespaceURI . ':' . $child->localName;
        switch ($absoluteNodeName) {
        case $this->lookupNamespace('atom') . ':' . 'entry':
            $newEntry = new $this->_entryClassName($child);
            $newEntry->setHttpClient($this->getHttpClient());
            $newEntry->setMajorProtocolVersion($this->getMajorProtocolVersion());
            $newEntry->setMinorProtocolVersion($this->getMinorProtocolVersion());
            $this->_entry[] = $newEntry;
            break;
        default:
            parent::takeChildFromDOM($child);
            break;
        }
    }

You will note that the protocol version for the new entry is set after it has been created. Its creation, however, recursively processes all the DOM elements under it. During this processing, the entry object believes its protocol version to be the default, which is 1. Consequently some other element lookups will go wrong due to an apparent namespace mismatch:


        case $this->lookupNamespace('app') . ':' . 'control':

The namespace is looked up:


    public function lookupNamespace($prefix,
                                    $majorVersion = null,
                                    $minorVersion = null)
    {
        // Auto-select current version
        if ($majorVersion === null) {
            $majorVersion = $this->getMajorProtocolVersion();
        }
        if ($minorVersion === null) {
            $minorVersion = $this->getMinorProtocolVersion();
        }

        // Perform lookup
        return parent::lookupNamespace($prefix, $majorVersion, $minorVersion);
    }

At the time of the lookup, the entry's protocol version has not yet been set correctly, yet its value affects the lookup of the namespace. In this particular example is not being recognized as the default major protocol version is 1 and thus the namespace does not get recognized (only available in version 2) and the corresponding element is not treated correctly.

My fix has been to change the constructor of all "entry" elements to accept a major and minor version number and to pass them to the constructor of the element:


    protected function takeChildFromDOM($child)
    {
        $absoluteNodeName = $child->namespaceURI . ':' . $child->localName;
        switch ($absoluteNodeName) {
        case $this->lookupNamespace('atom') . ':' . 'entry':
            $newEntry = new $this->_entryClassName($child, $this->getMajorProtocolVersion(), $this->getMinorProtocolVersion());
            $newEntry->setHttpClient($this->getHttpClient());
            $this->_entry[] = $newEntry;
            break;
        default:
            parent::takeChildFromDOM($child);
            break;
        }
    }

I will not repeat the 5 or 6 cases where the constructor needs to be changed, except at the root level, where the incoming values need to be set (unless null for backward compatability):


    public function __construct($element = null, $major = null, $minor = null)
    {
        if (!is_null($major)) {
            $this->setMajorProtocolVersion($major);
        }
        if (!is_null($minor)) {
            $this->setMinorProtocolVersion($minor);
        }
        if (!($element instanceof DOMElement)) {
            if ($element) {
                $this->transferFromXML($element);
            }
        } else {
            $this->transferFromDOM($element);
        }
    }

Comments

Yet another place where protocol version is handled wrong


protected function takeChildFromDOM($child)
    {
        $absoluteNodeName = $child->namespaceURI . ':' . $child->localName;
        switch ($absoluteNodeName) {
        case $this->lookupNamespace('app') . ':' . 'draft':
            $draft = new Zend_Gdata_App_Extension_Draft();
            $draft->transferFromDOM($child);
            $this->_draft = $draft;
            break;
        default:
            parent::takeChildFromDOM($child);
            break;
        }
    }

and now look at the lookupNamespace method, which always use version majorVersion = 1


public function lookupNamespace($prefix,
                                    $majorVersion = 1,
                                    $minorVersion = null)
    {
        // Check for a memoized result
        $key = $prefix . ' ' .
               ($majorVersion === null ? 'NULL' : $majorVersion) .
               ' '. ($minorVersion === null ? 'NULL' : $minorVersion);

Simple search over library code for lookupNamespace and majorVersion gets a lot of possible issues with protocol 2 support. Looks like v2 support is completely broken in current version of library.

Google should clearly state in their documentation that v2 is not only "not enabled yet" but is unsupported.

{quote} As of version 1.7.4 of the PHP Client Library, the version 2 specific functionality has not yet been enabled by default. You can use the setMajorProtocolVersion function to set a version for either a service class, a feed or an entry. A version 2 specific service class will always return v2 feeds and entries. {quote}

oops, lookupNamespace method is in Zend_Gdata_App_Base