Completed
Push — master ( 2472c5...a814e8 )
by mw
35:03
created

src/DataValues/MonolingualTextValue.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace SMW\DataValues;
4
5
use SMW\ApplicationFactory;
6
use SMW\DataValueFactory;
7
use SMW\DataValues\ValueFormatters\DataValueFormatter;
8
use SMW\DataValues\ValueParsers\MonolingualTextValueParser;
9
use SMW\DIProperty;
10
use SMW\DIWikiPage;
11
use SMW\Localizer;
12
use SMW\StringCondition;
13
use SMWContainerSemanticData as ContainerSemanticData;
14
use SMWDataItem as DataItem;
15
use SMWDataValue as DataValue;
16
use SMWDIContainer as DIContainer;
17
use SMWDIBlob as DIBlob;
18
19
/**
20
 * MonolingualTextValue requires two components, a language code and a
21
 * text.
22
 *
23
 * A text `foo@en` is expected to be invoked with a BCP47 language
24
 * code tag and a language dependent text component.
25
 *
26
 * Internally, the value is stored as container object that represents
27
 * the language code and text as separate entities in order to be queried
28
 * individually.
29
 *
30
 * External output representation depends on the context (wiki, html)
31
 * whether the language code is omitted or not.
32
 *
33
 * @license GNU GPL v2+
34
 * @since 2.4
35
 *
36
 * @author mwjames
37
 */
38
class MonolingualTextValue extends AbstractMultiValue {
39
40
	/**
41
	 * @var DIProperty[]|null
42
	 */
43
	private static $properties = null;
44
45
	/**
46
	 * @var MonolingualTextValueParser
47
	 */
48
	private $monolingualTextValueParser = null;
49
50
	/**
51
	 * @param string $typeid
52
	 */
53 29
	public function __construct( $typeid = '' ) {
54 29
		parent::__construct( '_mlt_rec' );
55 29
	}
56
57
	/**
58
	 * @see AbstractMultiValue::setFieldProperties
59
	 *
60
	 * @param DIProperty[] $properties
61
	 */
62
	public function setFieldProperties( array $properties ) {
63
		// Keep the interface while the properties for this type
64
		// are fixed.
65
	}
66
67
	/**
68
	 * @see AbstractMultiValue::getProperties
69
	 *
70
	 * @param DIProperty[] $properties
71
	 */
72
	public function getProperties() {
73
		self::$properties;
74
	}
75
76
	/**
77
	 * @since 2.5
78
	 *
79
	 * @param $userValue
80
	 * @param string $languageCode
81
	 *
82
	 * @return string
83
	 */
84 1
	public function getTextWithLanguageTag( $text, $languageCode ) {
85 1
		return $text . '@' . Localizer::asBCP47FormattedLanguageCode( $languageCode );
86
	}
87
88
	/**
89
	 * @see DataValue::parseUserValue
90
	 * @note called by DataValue::setUserValue
91
	 *
92
	 * @param string $userValue
93
	 */
94 26
	protected function parseUserValue( $userValue ) {
95
96 26
		list( $text, $languageCode ) = $this->getValuesFromString( $userValue );
97
98 26
		$languageCodeValue = $this->newLanguageCodeValue( $languageCode );
99
100
		if (
101 26
			( $languageCode !== '' && $languageCodeValue->getErrors() !== array() ) ||
102 26
			( $languageCode === '' && $this->isEnabledFeature( SMW_DV_MLTV_LCODE ) ) ) {
103 3
			$this->addError( $languageCodeValue->getErrors() );
104 3
			return;
105
		}
106
107 23
		$dataValues = array();
108
109 23
		foreach ( $this->getPropertyDataItems() as $property ) {
110
111
			if (
112 23
				( $languageCode === '' && $property->getKey() === '_LCODE' ) ||
113 23
				( $text === '' && $property->getKey() === '_TEXT' ) ) {
114 1
				continue;
115
			}
116
117 23
			$value = $text;
118
119 23
			if ( $property->getKey() === '_LCODE' ) {
120 22
				$value = $languageCode;
121
			}
122
123 23
			$dataValue = DataValueFactory::getInstance()->newDataValueByProperty(
124
				$property,
125
				$value,
126 23
				false,
127 23
				$this->m_contextPage
128
			);
129
130 23
			$this->addError( $dataValue->getErrors() );
131
132 23
			$dataValues[] = $dataValue;
133
		}
134
135
		// Generate a hash from the normalized representation so that foo@en being
136
		// the same as foo@EN independent of a user input
137 23
		$containerSemanticData = $this->newContainerSemanticData( $text . '@' . $languageCode );
138
139 23
		foreach ( $dataValues as $dataValue ) {
140 23
			$containerSemanticData->addDataValue( $dataValue );
141
		}
142
143 23
		$this->m_dataitem = new DIContainer( $containerSemanticData );
144
145
		// Composite sortkey is to ensure that Store::getPropertyValues can
146
		// apply sorting during value selection
147 23
		$this->m_dataitem->addCompositeSortKey( implode( ';', array( $text, $languageCode ) ) );
148 23
	}
149
150
	/**
151
	 * @note called by MonolingualTextValueDescriptionDeserializer::deserialize
152
	 * and MonolingualTextValue::parseUserValue
153
	 *
154
	 * No explicit check is made on the validity of a language code and is
155
	 * expected to be done before calling this method.
156
	 *
157
	 * @since 2.4
158
	 *
159
	 * @param string $userValue
160
	 *
161
	 * @return array
162
	 */
163 26
	public function getValuesFromString( $userValue ) {
164 26
		return $this->getValueParser()->parse( $userValue );
165
	}
166
167
	/**
168
	 * @see DataValue::loadDataItem
169
	 *
170
	 * @param DataItem $dataItem
171
	 *
172
	 * @return boolean
173
	 */
174 14
	protected function loadDataItem( DataItem $dataItem ) {
175
176 14
		if ( $dataItem->getDIType() === DataItem::TYPE_CONTAINER ) {
177
			$this->m_dataitem = $dataItem;
178
			return true;
179 14
		} elseif ( $dataItem->getDIType() === DataItem::TYPE_WIKIPAGE ) {
180 14
			$semanticData = new ContainerSemanticData( $dataItem );
181 14
			$semanticData->copyDataFrom( ApplicationFactory::getInstance()->getStore()->getSemanticData( $dataItem ) );
182 14
			$this->m_dataitem = new DIContainer( $semanticData );
183 14
			return true;
184
		}
185
186 3
		return false;
187
	}
188
189
	/**
190
	 * @see DataValue::getShortWikiText
191
	 */
192 16
	public function getShortWikiText( $linker = null ) {
193 16
		return $this->getDataValueFormatter()->format( DataValueFormatter::WIKI_SHORT, $linker );
194
	}
195
196
	/**
197
	 * @see DataValue::getShortHTMLText
198
	 */
199
	public function getShortHTMLText( $linker = null ) {
200
		return $this->getDataValueFormatter()->format( DataValueFormatter::HTML_SHORT, $linker );
201
	}
202
203
	/**
204
	 * @see DataValue::getLongWikiText
205
	 */
206
	public function getLongWikiText( $linker = null ) {
207
		return $this->getDataValueFormatter()->format( DataValueFormatter::WIKI_LONG, $linker );
208
	}
209
210
	/**
211
	 * @see DataValue::getLongHTMLText
212
	 */
213
	public function getLongHTMLText( $linker = null ) {
214
		return $this->getDataValueFormatter()->format( DataValueFormatter::HTML_LONG, $linker );
215
	}
216
217
	/**
218
	 * @see DataValue::getWikiValue
219
	 */
220 5
	public function getWikiValue() {
221 5
		return $this->getDataValueFormatter()->format( DataValueFormatter::VALUE );
222
	}
223
224
	/**
225
	 * @since 2.4
226
	 * @note called by AbstractRecordValue::getPropertyDataItems
227
	 *
228
	 * @return DIProperty[]
229
	 */
230 24
	public function getPropertyDataItems() {
231
232 24
		if ( self::$properties !== null && self::$properties !== array() ) {
233 23
			return self::$properties;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return self::$properties; (SMW\DIProperty[]) is incompatible with the return type declared by the abstract method SMW\DataValues\AbstractM...e::getPropertyDataItems of type SMW\DataValues\DIProperty[]|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
234
		}
235
236 1
		foreach ( array( '_TEXT', '_LCODE' ) as  $id ) {
237 1
			self::$properties[] = new DIProperty( $id );
238
		}
239
240 1
		return self::$properties;
241
	}
242
243
	/**
244
	 * @since 2.4
245
	 *
246
	 * {@inheritDoc}
247
	 */
248 2
	public function getDataItems() {
249 2
		return parent::getDataItems();
250
	}
251
252
	/**
253
	 * @since 2.4
254
	 *
255
	 * @param string $languageCode
256
	 *
257
	 * @return DataValue|null
258
	 */
259 10
	public function getTextValueByLanguage( $languageCode ) {
260
261 10
		if ( ( $list = $this->toArray() ) === array() ) {
262
			return null;
263
		}
264
265 10
		if ( $list['_LCODE'] !== Localizer::asBCP47FormattedLanguageCode( $languageCode ) ) {
266 3
			return null;
267
		}
268
269 9
		if ( $list['_TEXT'] === '' ) {
270
			return null;
271
		}
272
273 9
		$dataValue = DataValueFactory::getInstance()->newDataValueByItem(
274 9
			new DIBlob( $list['_TEXT'] ),
275 9
			new DIProperty( '_TEXT' )
276
		);
277
278 9
		return $dataValue;
279
	}
280
281
	/**
282
	 * @since 2.5
283
	 *
284
	 * @return array
285
	 */
286 14
	public function toArray() {
287
288 14
		if ( !$this->isValid() || $this->getDataItem() === array() ) {
289
			return array();
290
		}
291
292 14
		$semanticData = $this->getDataItem()->getSemanticData();
293
294
		$list = array(
295 14
			'_TEXT'  => '',
296
			'_LCODE' => ''
297
		);
298
299 14
		$dataItems = $semanticData->getPropertyValues( new DIProperty( '_TEXT' ) );
300 14
		$dataItem = reset( $dataItems );
301
302 14
		if ( $dataItem !== false  ) {
303 14
			$list['_TEXT'] = $dataItem->getString();
304
		}
305
306 14
		$dataItems = $semanticData->getPropertyValues( new DIProperty( '_LCODE' ) );
307 14
		$dataItem = reset( $dataItems );
308
309 14
		if ( $dataItem !== false ) {
310 14
			$list['_LCODE'] = $dataItem->getString();
311
		}
312
313 14
		return $list;
314
	}
315
316
	/**
317
	 * @since 2.5
318
	 *
319
	 * @return string
320
	 */
321 1
	public function toString() {
322
323 1
		if ( !$this->isValid() || $this->getDataItem() === array() ) {
324
			return '';
325
		}
326
327 1
		$list = $this->toArray();
328
329 1
		return $list['_TEXT'] . '@' . $list['_LCODE'];
330
	}
331
332 23
	private function newContainerSemanticData( $value ) {
333
334 23
		if ( $this->m_contextPage === null ) {
335 6
			$containerSemanticData = ContainerSemanticData::makeAnonymousContainer();
336 6
			$containerSemanticData->skipAnonymousCheck();
337
		} else {
338 17
			$subobjectName = '_ML' . md5( $value );
339
340 17
			$subject = new DIWikiPage(
341 17
				$this->m_contextPage->getDBkey(),
342 17
				$this->m_contextPage->getNamespace(),
343 17
				$this->m_contextPage->getInterwiki(),
344
				$subobjectName
345
			);
346
347 17
			$containerSemanticData = new ContainerSemanticData( $subject );
348
		}
349
350 23
		return $containerSemanticData;
351
	}
352
353 26
	private function newLanguageCodeValue( $languageCode ) {
354
355 26
		$languageCodeValue = new LanguageCodeValue();
356
357 26
		if ( $this->m_property !== null ) {
358 17
			$languageCodeValue->setProperty( $this->m_property );
359
		}
360
361 26
		$languageCodeValue->setUserValue( $languageCode );
362
363 26
		return $languageCodeValue;
364
	}
365
366 26
	private function getValueParser() {
367
368 26
		if ( $this->monolingualTextValueParser === null ) {
369 26
			$this->monolingualTextValueParser = ValueParserFactory::getInstance()->newMonolingualTextValueParser();
370
		}
371
372 26
		return $this->monolingualTextValueParser;
373
	}
374
375
}
376