JSONSerializer   B
last analyzed

Complexity

Total Complexity 45

Size/Duplication

Total Lines 181
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 3

Test Coverage

Coverage 31.25%

Importance

Changes 2
Bugs 2 Features 0
Metric Value
wmc 45
lcom 0
cbo 3
dl 0
loc 181
ccs 35
cts 112
cp 0.3125
rs 8.3673
c 2
b 2
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
C serialize() 0 31 7
D serializeContact() 0 37 10
F serializeProperty() 0 83 28

How to fix   Complexity   

Complex Class

Complex classes like JSONSerializer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use JSONSerializer, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * ownCloud - JSONSerializer
4
 *
5
 * @author Thomas Tanghus
6
 * @copyright 2013-2014 Thomas Tanghus ([email protected])
7
 *
8
 * This library is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
10
 * License as published by the Free Software Foundation; either
11
 * version 3 of the License, or any later version.
12
 *
13
 * This library is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
17
 *
18
 * You should have received a copy of the GNU Affero General Public
19
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
20
 *
21
 */
22
23
namespace OCA\Contacts\Utils;
24
25
use OCA\Contacts\VObject;
26
use OCA\Contacts\Contact;
27
use OCA\Contacts\Utils\Properties;
28
29
/**
30
 * This class serializes properties, components an
31
 * arrays of components into a format suitable for
32
 * passing to a JSON response.
33
 * TODO: Return jCard (almost) compliant data, but still omitting unneeded data.
34
 * http://tools.ietf.org/html/draft-kewisch-vcard-in-json-01
35
 */
36
37
class JSONSerializer {
38
39
	/**
40
	 * General method serialize method. Use this for arrays
41
	 * of contacts.
42
	 *
43
	 * @param Contact[] $input
44
	 * @return array
45
	 */
46
	 public static function serialize($input) {
47
		$response = array();
48
		if(is_array($input)) {
49
			foreach($input as $object) {
50
				if($object instanceof Contact) {
51
					\OCP\Util::writeLog('contacts', __METHOD__.' serializing: ' . print_r($object, true), \OCP\Util::DEBUG);
52
					$tmp = self::serializeContact($object);
53
					if($tmp !== null) {
54
						$response[] = $tmp;
55
					}
56
				} else {
57
					throw new \Exception(
58
						'Only arrays of OCA\\Contacts\\VObject\\VCard '
59
						. 'and Sabre\VObject\Property are accepted.'
60
					);
61
				}
62
			}
63
		} else {
64
			if($input instanceof VObject\VCard) {
65
				return self::serializeContact($input);
66
			} elseif($input instanceof \Sabre\VObject\Property) {
67
				return self::serializeProperty($input);
68
			} else {
69
				throw new \Exception(
70
					'Only instances of OCA\\Contacts\\VObject\\VCard '
71
					. 'and Sabre\VObject\Property are accepted.'
72
				);
73
			}
74
		}
75
		return $response;
76
	 }
77
78
	/**
79
	 * @brief Data structure of vCard
80
	 * @param VObject\VCard $contact
81
	 * @return associative array|null
82
	 */
83 2
	public static function serializeContact(Contact $contact) {
84
85 2
		if(!$contact->retrieve()) {
86
			\OCP\Util::writeLog('contacts', __METHOD__.' error reading: ' . print_r($contact, true), \OCP\Util::DEBUG);
87
			return null;
88
		}
89
90 2
		$details = array();
91
92 2
		if(isset($contact->PHOTO) || isset($contact->LOGO)) {
93
			$details['photo'] = true;
94
		}
95
96 2
		foreach($contact->children as $property) {
97 2
			$pname = $property->name;
98 2
			$temp = self::serializeProperty($property);
99 2
			if(!is_null($temp)) {
100
				// Get Apple X-ABLabels
101 2
				if(isset($contact->{$property->group . '.X-ABLABEL'})) {
102
					$temp['label'] = $contact->{$property->group . '.X-ABLABEL'}->getValue();
103
					if($temp['label'] == '_$!<Other>!$_') {
104
						$temp['label'] = Properties::$l10n->t('Other');
105
					}
106
					if($temp['label'] == '_$!<HomePage>!$_') {
107
						$temp['label'] = Properties::$l10n->t('HomePage');
108
					}
109
				}
110 2
				if(array_key_exists($pname, $details)) {
111
					$details[$pname][] = $temp;
112
				}
113
				else{
114 2
					$details[$pname] = array($temp);
115
				}
116 2
			}
117 2
		}
118 2
		return array('data' =>$details, 'metadata' => $contact->getMetaData());
119
	}
120
121
	/**
122
	 * @brief Get data structure of property.
123
	 * @param \Sabre\VObject\Property $property
124
	 * @return associative array
125
	 *
126
	 * returns an associative array with
127
	 * ['name'] name of property
128
	 * ['value'] htmlspecialchars escaped value of property
129
	 * ['parameters'] associative array name=>value
130
	 * ['checksum'] checksum of whole property
131
	 * NOTE: $value is not escaped anymore. It shouldn't make any difference
132
	 * but we should look out for any problems.
133
	 */
134 2
	public static function serializeProperty(\Sabre\VObject\Property $property) {
135 2
		if(!in_array($property->name, Properties::$indexProperties)) {
136 2
			return;
137
		}
138 2
		$value = $property->getValue();
0 ignored issues
show
Bug introduced by
The method getValue() does not seem to exist on object<Sabre\VObject\Property>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
139 2
		if($property->name == 'ADR' || $property->name == 'N' || $property->name == 'ORG' || $property->name == 'CATEGORIES') {
140 2
			$value = $property->getParts();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Sabre\VObject\Property as the method getParts() does only exist in the following sub-classes of Sabre\VObject\Property: Sabre\VObject\Property\Compound. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
141 2
			$value = array_map('trim', $value);
142 2
		}
143 2
		elseif($property->name == 'BDAY') {
144
			// If the BDAY has a format of e.g. 19960401
145
			if(strlen($value) >= 8
146
				&& is_int(substr($value, 0, 4))
147
				&& is_int(substr($value, 4, 2))
148
				&& is_int(substr($value, 6, 2))) {
149
				$value = substr($value, 0, 4).'-'.substr($value, 4, 2).'-'.substr($value, 6, 2);
150
			} else if($value[5] !== '-' || $value[7] !== '-') {
151
				try {
152
					// Skype exports as e.g. Jan 14, 1996
153
					$date = new \DateTime($value);
154
					$value = $date->format('Y-m-d');
155
				} catch(\Exception $e) {
156
					\OCP\Util::writeLog('contacts', __METHOD__.' Error parsing date: ' . $value, \OCP\Util::DEBUG);
157
					return;
158
				}
159
			}
160
			// Otherwise we assume it's OK.
161 2
		} elseif($property->name == 'PHOTO') {
162
			$value = true;
163
		}
164 2
		elseif($property->name == 'IMPP') {
165
			if(strpos($value, ':') !== false) {
166
				$value = explode(':', $value);
167
				$protocol = array_shift($value);
168
				if(!isset($property['X-SERVICE-TYPE'])) {
169
					$property['X-SERVICE-TYPE'] = strtoupper($protocol);
170
				}
171
				$value = implode('', $value);
172
			}
173
		}
174 2
		if(is_string($value)) {
175 2
			$value = strtr($value, array('\,' => ',', '\;' => ';'));
176 2
		}
177
		$temp = array(
178
			//'name' => $property->name,
179 2
			'value' => $value,
180 2
			'parameters' => array()
181 2
		);
182
183
		// This cuts around a 3rd off of the json response size.
184 2
		if(in_array($property->name, Properties::$multiProperties)) {
185
			$temp['checksum'] = substr(md5($property->serialize()), 0, 8);
186
		}
187 2
		foreach($property->parameters as $parameter) {
188
			// Faulty entries by kaddressbook
189
			// Actually TYPE=PREF is correct according to RFC 2426
190
			// but this way is more handy in the UI. Tanghus.
191
			if($parameter->name == 'TYPE' && strtoupper($parameter->getValue()) == 'PREF') {
192
				$parameter->name = 'PREF';
193
				$parameter->setValue('1');
194
			}
195
			// NOTE: Apparently \Sabre\VObject\Reader can't always deal with value list parameters
196
			// like TYPE=HOME,CELL,VOICE. Tanghus.
197
			// TODO: Check if parameter is has commas and split + merge if so.
198
			if ($parameter->name == 'TYPE') {
199
				$pvalue = $parameter->getValue();
200
				if(is_string($pvalue) && strpos($pvalue, ',') !== false) {
201
					$pvalue = array_map('trim', explode(',', $pvalue));
202
				}
203
				$pvalue = is_array($pvalue) ? $pvalue : array($pvalue);
204
				if (isset($temp['parameters'][$parameter->name])) {
205
					$temp['parameters'][$parameter->name][] = \OCP\Util::sanitizeHTML($pvalue);
206
				}
207
				else {
208
					$temp['parameters'][$parameter->name] = \OCP\Util::sanitizeHTML($pvalue);
209
				}
210
			}
211
			else{
212
				$temp['parameters'][$parameter->name] = \OCP\Util::sanitizeHTML($parameter->getValue());
213
			}
214 2
		}
215 2
		return $temp;
216
	}
217
}
218