summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/SemanticMediaWiki/includes/datavalues/SMW_DV_Record.php
blob: f4bab7e618309b66ea8003296cc4a7873a488fc2 (plain)
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
<?php

use SMW\ApplicationFactory;
use SMW\DataValueFactory;
use SMW\DataValues\AbstractMultiValue;
use SMW\DIProperty;
use SMW\DIWikiPage;
use SMWContainerSemanticData as ContainerSemanticData;
use SMWDataItem as DataItem;
use SMWDIContainer as DIContainer;

/**
 * SMWDataValue implements the handling of small sets of property-value pairs.
 * The declaration of Records in SMW uses the order of values to encode the
 * property that should be used, so the user only needs to enter a list of
 * values. Internally, however, the property-value assignments are not stored
 * with a particular order; they will only be ordered for display, following
 * the declaration. This is why it is not supported to have Records using the
 * same property for more than one value.
 *
 * The class uses DIContainer objects to return its inner state. See the
 * documentation for DIContainer for details on how this "pseudo" data
 * encapsulated many property assignments. Such data is stored internally
 * like a page with various property-value assignments. Indeed, record values
 * can be created from DIWikiPage objects (the missing information will
 * be fetched from the store).
 *
 * @todo Enforce limitation of maximal number of values.
 * @todo Enforce uniqueness of properties in declaration.
 * @todo Complete internationalisation.
 *
 * @author Markus Krötzsch
 * @ingroup SMWDataValues
 */
class SMWRecordValue extends AbstractMultiValue {

	/// cache for properties for the fields of this data value
	protected $m_diProperties = null;

	/**
	 * @param string $typeid
	 */
	public function __construct( $typeid = '' ) {
		parent::__construct( '_rec' );
	}

	/**
	 * @since 2.3
	 *
	 * @return DIProperty[]|null
	 */
	public function getProperties() {
		return $this->m_diProperties;
	}

	/**
	 * @since 2.3
	 *
	 * @param string $value
	 *
	 * @return array
	 */
	public function getValuesFromString( $value ) {
		// #664 / T17732
		$value = str_replace( "\;", "-3B", $value );

		// Bug 21926 / T23926
		// Values that use html entities are encoded with a semicolon
		$value = htmlspecialchars_decode( $value, ENT_QUOTES );
		$values = preg_split( '/[\s]*;[\s]*/u', trim( $value ) );

		return str_replace( "-3B", ";", $values );
	}

	protected function parseUserValue( $value ) {

		if ( $value === '' ) {
			$this->addErrorMsg( [ 'smw_novalues' ] );
			return;
		}

		$containerSemanticData = $this->newContainerSemanticData( $value );
		$sortKeys = [];

		$values = $this->getValuesFromString( $value );
		$valueIndex = 0; // index in value array
		$propertyIndex = 0; // index in property list
		$empty = true;

		foreach ( $this->getPropertyDataItems() as $diProperty ) {

			if ( !array_key_exists( $valueIndex, $values ) || $this->getErrors() !== [] ) {
				break; // stop if there are no values left
			}

			// generating the DVs:
			if ( ( $values[$valueIndex] === '' ) || ( $values[$valueIndex] == '?' ) ) { // explicit omission
				$valueIndex++;
			} else {
				$dataValue = DataValueFactory::getInstance()->newDataValueByProperty(
					$diProperty,
					$values[$valueIndex],
					false,
					$containerSemanticData->getSubject()
				);

				if ( $dataValue->isValid() ) { // valid DV: keep
					$containerSemanticData->addPropertyObjectValue( $diProperty, $dataValue->getDataItem() );
					$sortKeys[] = $dataValue->getDataItem()->getSortKey();

					$valueIndex++;
					$empty = false;
				} elseif ( ( count( $values ) - $valueIndex ) == ( count( $this->m_diProperties ) - $propertyIndex ) ) {
					$containerSemanticData->addPropertyObjectValue( $diProperty, $dataValue->getDataItem() );
					$this->addError( $dataValue->getErrors() );
					++$valueIndex;
				}
			}
			++$propertyIndex;
		}

		if ( $empty && $this->getErrors() === []  ) {
			$this->addErrorMsg( [ 'smw_novalues' ] );
		}

		// Remember the data to extend the sortkey
		$containerSemanticData->setExtensionData( 'sort.data', implode( ';', $sortKeys ) );

		$this->m_dataitem = new DIContainer( $containerSemanticData );
	}

	/**
	 * @see SMWDataValue::loadDataItem()
	 * @param $dataitem DataItem
	 * @return boolean
	 */
	protected function loadDataItem( DataItem $dataItem ) {
		if ( $dataItem->getDIType() == DataItem::TYPE_CONTAINER ) {
			$this->m_dataitem = $dataItem;
			return true;
		} elseif ( $dataItem->getDIType() == DataItem::TYPE_WIKIPAGE ) {
			$semanticData = new ContainerSemanticData( $dataItem );
			$semanticData->copyDataFrom( ApplicationFactory::getInstance()->getStore()->getSemanticData( $dataItem ) );
			$this->m_dataitem = new DIContainer( $semanticData );
			return true;
		} else {
			return false;
		}
	}

	public function getShortWikiText( $linked = null ) {
		if ( $this->m_caption !== false ) {
			return $this->m_caption;
		}
		return $this->makeOutputText( 0, $linked );
	}

	public function getShortHTMLText( $linker = null ) {
		if ( $this->m_caption !== false ) {
			return $this->m_caption;
		}
		return $this->makeOutputText( 1, $linker );
	}

	public function getLongWikiText( $linked = null ) {
		return $this->makeOutputText( 2, $linked );
	}

	public function getLongHTMLText( $linker = null ) {
		return $this->makeOutputText( 3, $linker );
	}

	public function getWikiValue() {
		return $this->makeOutputText( 4 );
	}

	/**
	 * Make sure that the content is reset in this case.
	 * @todo This is not a full reset yet (the case that property is changed after a value
	 * was set does not occur in the normal flow of things, hence this has low priority).
	 */
	public function setProperty( DIProperty $property ) {
		parent::setProperty( $property );
		$this->m_diProperties = null;
	}

	/**
	 * @since 2.1
	 *
	 * @param DIProperty[] $properties
	 */
	public function setFieldProperties( array $properties ) {
		foreach ( $properties as $property ) {
			if ( $property instanceof DIProperty ) {
				$this->m_diProperties[] = $property;
			}
		}
	}

	/**
	 * @since 1.6
	 *
	 * {@inheritDoc}
	 */
	public function getDataItems() {
		return parent::getDataItems();
	}

	/**
	 * Return the array (list) of properties that the individual entries of
	 * this datatype consist of.
	 *
	 * @since 1.6
	 *
	 * @todo I18N for error message.
	 *
	 * @return array of DIProperty
	 */
	public function getPropertyDataItems() {

		if ( $this->m_diProperties !== null ) {
			return $this->m_diProperties;
		}

		$this->m_diProperties = $this->getFieldProperties( $this->m_property );

		if ( $this->m_diProperties  === [] ) { // TODO internalionalize
			$this->addError( 'The list of properties to be used for the data fields has not been specified properly.' );
		}

		return $this->m_diProperties;
	}

	protected function makeOutputText( $type = 0, $linker = null ) {
		if ( !$this->isValid() ) {
			return ( ( $type == 0 ) || ( $type == 1 ) ) ? '' : $this->getErrorText();
		}

		$result = '';
		$i = 0;
		foreach ( $this->getPropertyDataItems() as $propertyDataItem ) {
			if ( $i == 1 ) {
				$result .= ( $type == 4 ) ? '; ' : ' (';
			} elseif ( $i > 1 ) {
				$result .= ( $type == 4 ) ? '; ' : ', ';
			}
			++$i;
			$propertyValues = $this->m_dataitem->getSemanticData()->getPropertyValues( $propertyDataItem ); // combining this with next line violates PHP strict standards
			$dataItem = reset( $propertyValues );
			if ( $dataItem !== false ) {
				$dataValue = DataValueFactory::getInstance()->newDataValueByItem( $dataItem, $propertyDataItem );
				$result .= $this->makeValueOutputText( $type, $dataValue, $linker );
			} else {
				$result .= '?';
			}
		}
		if ( ( $i > 1 ) && ( $type != 4 ) ) {
			$result .= ')';
		}

		return $result;
	}

	protected function makeValueOutputText( $type, SMWDataValue $dataValue, $linker ) {
		switch ( $type ) {
			case 0:
			return $dataValue->getShortWikiText( $linker );
			case 1:
			return $dataValue->getShortHTMLText( $linker );
			case 2:
			return $dataValue->getShortWikiText( $linker );
			case 3:
			return $dataValue->getShortHTMLText( $linker );
			case 4:
			return str_replace( ";", "\;", $dataValue->getWikiValue() );
		}
	}

	private function newContainerSemanticData( $value ) {

		if ( $this->m_contextPage === null ) {
			$containerSemanticData = ContainerSemanticData::makeAnonymousContainer();
			$containerSemanticData->skipAnonymousCheck();
		} else {
			$subobjectName = '_' . hash( 'md4', $value, false ); // md4 is probably fastest of PHP's hashes

			$subject = new DIWikiPage(
				$this->m_contextPage->getDBkey(),
				$this->m_contextPage->getNamespace(),
				$this->m_contextPage->getInterwiki(),
				$subobjectName
			);

			$containerSemanticData = new ContainerSemanticData( $subject );
		}

		return $containerSemanticData;
	}

}