diff --git a/src/Kore/RatingFieldTypeBundle/DependencyInjection/Configuration.php b/src/Kore/RatingFieldTypeBundle/DependencyInjection/Configuration.php new file mode 100644 index 0000000000..c1ef4206ed --- /dev/null +++ b/src/Kore/RatingFieldTypeBundle/DependencyInjection/Configuration.php @@ -0,0 +1,29 @@ +root('kore_rating_field_type'); + + // Here you should define the parameters that are allowed to + // configure your bundle. See the documentation linked above for + // more information on that topic. + + return $treeBuilder; + } +} diff --git a/src/Kore/RatingFieldTypeBundle/DependencyInjection/KoreRatingFieldTypeExtension.php b/src/Kore/RatingFieldTypeBundle/DependencyInjection/KoreRatingFieldTypeExtension.php new file mode 100644 index 0000000000..a6c7be9f9c --- /dev/null +++ b/src/Kore/RatingFieldTypeBundle/DependencyInjection/KoreRatingFieldTypeExtension.php @@ -0,0 +1,36 @@ +prependExtensionConfig('ezpublish', $config); + } + + /** + * {@inheritdoc} + */ + public function load(array $configs, ContainerBuilder $container) + { + $configuration = new Configuration(); + $config = $this->processConfiguration($configuration, $configs); + + $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader->load('services.xml'); + } +} diff --git a/src/Kore/RatingFieldTypeBundle/KoreRatingFieldTypeBundle.php b/src/Kore/RatingFieldTypeBundle/KoreRatingFieldTypeBundle.php new file mode 100644 index 0000000000..2ae8c1a8b3 --- /dev/null +++ b/src/Kore/RatingFieldTypeBundle/KoreRatingFieldTypeBundle.php @@ -0,0 +1,9 @@ + + + + + + + + + + + + + + + + diff --git a/src/Kore/RatingFieldTypeBundle/Resources/views/field_edit.html.twig b/src/Kore/RatingFieldTypeBundle/Resources/views/field_edit.html.twig new file mode 100644 index 0000000000..2b8ddc6dbc --- /dev/null +++ b/src/Kore/RatingFieldTypeBundle/Resources/views/field_edit.html.twig @@ -0,0 +1 @@ +Edit? diff --git a/src/Kore/RatingFieldTypeBundle/Resources/views/field_view.html.twig b/src/Kore/RatingFieldTypeBundle/Resources/views/field_view.html.twig new file mode 100644 index 0000000000..84c5443671 --- /dev/null +++ b/src/Kore/RatingFieldTypeBundle/Resources/views/field_view.html.twig @@ -0,0 +1,6 @@ +{% block koreRating_field %} + {% set field_value %} + {{ field.value.rating }} + {% endset %} + {{ block( 'simple_block_field' ) }} +{% endblock %} diff --git a/src/Kore/RatingFieldTypeBundle/Resources/views/fielddefinition_settings.html.twig b/src/Kore/RatingFieldTypeBundle/Resources/views/fielddefinition_settings.html.twig new file mode 100644 index 0000000000..0ed0944762 --- /dev/null +++ b/src/Kore/RatingFieldTypeBundle/Resources/views/fielddefinition_settings.html.twig @@ -0,0 +1,3 @@ +{% block koreRating_settings %} + No settings +{% endblock %} diff --git a/src/Kore/RatingFieldTypeBundle/Storage/FieldType/Type.php b/src/Kore/RatingFieldTypeBundle/Storage/FieldType/Type.php new file mode 100644 index 0000000000..b710d8243a --- /dev/null +++ b/src/Kore/RatingFieldTypeBundle/Storage/FieldType/Type.php @@ -0,0 +1,208 @@ + + * protected function createValueFromInput( $inputValue ) + * { + * if ( is_array( $inputValue ) ) + * { + * $inputValue = \eZ\Publish\Core\FieldType\CookieJar\Value( $inputValue ); + * } + * + * return $inputValue; + * } + * + * + * @param mixed $inputValue + * + * @return mixed The potentially converted input value. + */ + protected function createValueFromInput($inputValue) + { + // @EXT: Default possible depending on value class, at least for + // trivial values + // + // There is probably a connection with the edit template, which is + // missing in the tutoorial. + if ($inputValue instanceof Value) { + return $inputValue; + } + + if (!is_numeric($inputValue)) { + return new Value(['rating' => false]); + } + + return new Value(['rating' => (int) $inputValue]); + } + + /** + * Throws an exception if value structure is not of expected format. + * + * Note that this does not include validation after the rules + * from validators, but only plausibility checks for the general data + * format. + * + * This is an operation method for {@see acceptValue()}. + * + * Example implementation: + * + * protected function checkValueStructure( Value $value ) + * { + * if ( !is_array( $value->cookies ) ) + * { + * throw new InvalidArgumentException( "An array of assorted cookies was expected." ); + * } + * } + * + * + * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException If the value does not match the expected structure. + * + * @param \eZ\Publish\Core\FieldType\Value $value + */ + protected function checkValueStructure(CoreValue $value) + { + // @EXT: Default possible depending on value + if (!$value->rating) { + throw new InvalidArgumentException( + '$value->rating', + 'Expected rating to be a number' + ); + } + } + + /** + * Returns the empty value for this field type. + * + * This value will be used, if no value was provided for a field of this + * type and no default value was specified in the field definition. It is + * also used to determine that a user intentionally (or unintentionally) + * did not set a non-empty value. + * + * @return \eZ\Publish\SPI\FieldType\Value + */ + public function getEmptyValue() + { + // @EXT: Default possible when we know about the value class + return new Value(); + } + + /** + * Returns a human readable string representation from the given $value. + * + * It will be used to generate content name and url alias if current field + * is designated to be used in the content name/urlAlias pattern. + * + * The used $value can be assumed to be already accepted by {@link * + * acceptValue()}. + * + * @deprecated Since 6.3/5.4.7, use \eZ\Publish\SPI\FieldType\Nameable + * @param \eZ\Publish\SPI\FieldType\Value $value + * + * @return string + */ + public function getName(SPIValue $value) + { + return (string) $value; + } + + /** + * Returns information for FieldValue->$sortKey relevant to the field type. + * + * Return value is mixed. It should be something which is sensible for + * sorting. + * + * It is up to the persistence implementation to handle those values. + * Common string and integer values are safe. + * + * For the legacy storage it is up to the field converters to set this + * value in either sort_key_string or sort_key_int. + * + * @param \eZ\Publish\Core\FieldType\Value $value + * + * @return mixed + */ + protected function getSortInfo(CoreValue $value) + { + return $this->getName($value); + } + + /** + * Converts an $hash to the Value defined by the field type. + * + * This is the reverse operation to {@link toHash()}. At least the hash + * format generated by {@link toHash()} must be converted in reverse. + * Additional formats might be supported in the rare case that this is + * necessary. See the class description for more details on a hash format. + * + * @param mixed $hash + * + * @return \eZ\Publish\SPI\FieldType\Value + */ + public function fromHash($hash) + { + if ($hash === null) { + return $this->getEmptyValue(); + } + + // The default constructor at least works for the top level objects. + // For more complex values a manual conversion is necessary. + return new Value($hash); + } + + /** + * Converts the given $value into a plain hash format. + * + * Converts the given $value into a plain hash format, which can be used to + * transfer the value through plain text formats, e.g. XML, which do not + * support complex structures like objects. See the class level doc block + * for additional information. See the class description for more details + * on a hash format. + * + * @param \eZ\Publish\SPI\FieldType\Value $value + * + * @return mixed + */ + public function toHash(SPIValue $value) + { + // Simplest way to ensure a deep structure is cloned and converted into + // scalars and has maps. + return json_decode(json_encode($value), true); + } +} diff --git a/src/Kore/RatingFieldTypeBundle/Storage/FieldType/Value.php b/src/Kore/RatingFieldTypeBundle/Storage/FieldType/Value.php new file mode 100644 index 0000000000..23bbc6bf35 --- /dev/null +++ b/src/Kore/RatingFieldTypeBundle/Storage/FieldType/Value.php @@ -0,0 +1,15 @@ +rating; + } +} diff --git a/src/Kore/RatingFieldTypeBundle/Storage/Legacy/Converter.php b/src/Kore/RatingFieldTypeBundle/Storage/Legacy/Converter.php new file mode 100644 index 0000000000..98d6afd5ef --- /dev/null +++ b/src/Kore/RatingFieldTypeBundle/Storage/Legacy/Converter.php @@ -0,0 +1,42 @@ +dataText = json_encode($value->data); + $storageFieldValue->sortKeyString = $value->sortKey; + $storageFieldValue->sortKeyInt = $value->sortKey; + } + + public function toFieldValue(StorageFieldValue $value, FieldValue $fieldValue) + { + $fieldValue->data = json_decode($value->dataText, true) ?: []; + $fieldValue->sortKey = $value->sortKeyInt ?? $value->sortKeyString; + } + + public function toStorageFieldDefinition(FieldDefinition $fieldDef, StorageFieldDefinition $storageDef) + { + } + + public function toFieldDefinition(StorageFieldDefinition $storageDef, FieldDefinition $fieldDef) + { + } + + public function getIndexColumn() + { + // How to decide between sort_key_(int|string) + // + // How do we get access to the currently used value class to reason + // about this? + return 'sort_key_int'; + } +} diff --git a/src/Kore/RatingFieldTypeBundle/Storage/Legacy/Gateway.php b/src/Kore/RatingFieldTypeBundle/Storage/Legacy/Gateway.php new file mode 100644 index 0000000000..a66abe207e --- /dev/null +++ b/src/Kore/RatingFieldTypeBundle/Storage/Legacy/Gateway.php @@ -0,0 +1,94 @@ + []]; + + /** + * @var \Doctrine\DBAL\Connection + */ + protected $connection; + + public function __construct(Connection $connection) + { + $this->connection = $connection; + } + + /** + * Stores the keyword list from $field->value->externalData. + * + * @param \eZ\Publish\SPI\Persistence\Content\Field + * @param int $contentTypeId + */ + public function storeFieldData(Field $field, $contentTypeId) + { + $this->virtualDatabase[self::RATING_TABLE][$field->id] = $field->value->externalData; + } + + /** + * Sets the list of assigned keywords into $field->value->externalData. + * + * @param \eZ\Publish\SPI\Persistence\Content\Field $field + */ + public function getFieldData(Field $field) + { + $field->value->externalData = ['rating' => 3]; + + if (isset($this->virtualDatabase[self::RATING_TABLE][$field->id])) { + $field->value->externalData = $this->virtualDatabase[self::RATING_TABLE][$field->id]; + } + } + + /** + * Retrieve the ContentType ID for the given $field. + * + * @param \eZ\Publish\SPI\Persistence\Content\Field $field + * + * @return int + */ + public function getContentTypeId(Field $field) + { + $query = $this->connection->createQueryBuilder(); + $query + ->select($this->connection->quoteIdentifier('contentclass_id')) + ->from($this->connection->quoteIdentifier('ezcontentclass_attribute')) + ->where( + $query->expr()->eq('id', ':fieldDefinitionId') + ) + ->setParameter(':fieldDefinitionId', $field->fieldDefinitionId); + + $statement = $query->execute(); + + $row = $statement->fetch(\PDO::FETCH_ASSOC); + + if ($row === false) { + throw new RuntimeException( + sprintf( + 'Content Type ID cannot be retrieved based on the field definition ID "%s"', + $field->fieldDefinitionId + ) + ); + } + + return intval($row['contentclass_id']); + } + + /** + * Deletes keyword data for the given $fieldId. + * + * @param int $fieldId + */ + public function deleteFieldData($fieldId) + { + unset($this->virtualDatabase[self::RATING_TABLE][$fieldId]); + } +} diff --git a/src/Kore/RatingFieldTypeBundle/Storage/Legacy/Storage.php b/src/Kore/RatingFieldTypeBundle/Storage/Legacy/Storage.php new file mode 100644 index 0000000000..d77b647279 --- /dev/null +++ b/src/Kore/RatingFieldTypeBundle/Storage/Legacy/Storage.php @@ -0,0 +1,84 @@ +gateway->getContentTypeId($field); + + return $this->gateway->storeFieldData($field, $contentTypeId); + } + + /** + * Populates $field value property based on the external data. + * $field->value is a {@link eZ\Publish\SPI\Persistence\Content\FieldValue} object. + * This value holds the data as a {@link eZ\Publish\Core\FieldType\Value} based object, + * according to the field type (e.g. for TextLine, it will be a {@link eZ\Publish\Core\FieldType\TextLine\Value} object). + * + * @param \eZ\Publish\SPI\Persistence\Content\Field $field + * @param array $context + */ + public function getFieldData(VersionInfo $versionInfo, Field $field, array $context) + { + // @todo: This should already retrieve the ContentType ID + return $this->gateway->getFieldData($field); + } + + /** + * @param \eZ\Publish\SPI\Persistence\Content\VersionInfo $versionInfo + * @param array $fieldIds + * @param array $context + * + * @return bool + */ + public function deleteFieldData(VersionInfo $versionInfo, array $fieldIds, array $context) + { + // If current version being asked to be deleted is not published, then don't delete keywords + // if there is some other version which is published (as keyword table is not versioned) + if ($versionInfo->status !== VersionInfo::STATUS_PUBLISHED && + $versionInfo->contentInfo->isPublished + ) { + return false; + } + + foreach ($fieldIds as $fieldId) { + $this->gateway->deleteFieldData($fieldId); + } + + return true; + } + + /** + * Checks if field type has external data to deal with. + * + * @return bool + */ + public function hasFieldData() + { + return true; + } + + /** + * @param \eZ\Publish\SPI\Persistence\Content\VersionInfo $versionInfo + * @param \eZ\Publish\SPI\Persistence\Content\Field $field + * @param array $context + * @return \eZ\Publish\SPI\Search\Field[]|null + */ + public function getIndexData(VersionInfo $versionInfo, Field $field, array $context) + { + return null; + } +} diff --git a/src/Kore/RatingFieldTypeBundle/Tests/TypeIntegrationTest.php b/src/Kore/RatingFieldTypeBundle/Tests/TypeIntegrationTest.php new file mode 100644 index 0000000000..3349cae90a --- /dev/null +++ b/src/Kore/RatingFieldTypeBundle/Tests/TypeIntegrationTest.php @@ -0,0 +1,114 @@ +getHandler( + 'koreRating', + $fieldType, + new Converter(), + new Storage( + new Gateway( + $this->getDatabaseHandler()->getConnection() + ) + ) + ); + } + + /** + * Returns the FieldTypeConstraints to be used to create a field definition + * of the FieldType under test. + * + * @return \eZ\Publish\SPI\Persistence\Content\FieldTypeConstraints + */ + public function getTypeConstraints() + { + return new Content\FieldTypeConstraints(); + } + + /** + * Returns the field definition data expected after loading the newly + * created field definition with the FieldType under test. + * + * This is a PHPUnit data provider + * + * @return array + */ + public function getFieldDefinitionData() + { + return array( + // The ezkeyword field type does not have any special field definition + // properties + array('fieldType', 'koreRating'), + array('fieldTypeConstraints', new Content\FieldTypeConstraints()), + ); + } + + /** + * Get initial field value. + * + * @return \eZ\Publish\SPI\Persistence\Content\FieldValue + */ + public function getInitialValue() + { + return new Content\FieldValue( + array( + 'data' => array(), + 'externalData' => array('rating' => 3), + 'sortKey' => '3', + ) + ); + } + + /** + * Get update field value. + * + * Use to update the field + * + * @return \eZ\Publish\SPI\Persistence\Content\FieldValue + */ + public function getUpdatedValue() + { + return new Content\FieldValue( + array( + 'data' => array(), + 'externalData' => array('rating' => 5), + 'sortKey' => '5', + ) + ); + } +} diff --git a/src/Kore/RatingFieldTypeBundle/Tests/TypeTest.php b/src/Kore/RatingFieldTypeBundle/Tests/TypeTest.php new file mode 100644 index 0000000000..1ad84bc66d --- /dev/null +++ b/src/Kore/RatingFieldTypeBundle/Tests/TypeTest.php @@ -0,0 +1,248 @@ + + * return array( + * array( + * new \stdClass(), + * 'eZ\\Publish\\Core\\Base\\Exceptions\\InvalidArgumentException', + * ), + * array( + * array(), + * 'eZ\\Publish\\Core\\Base\\Exceptions\\InvalidArgumentException', + * ), + * // ... + * ); + * + * + * @return array + */ + public function provideInvalidInputForAcceptValue() + { + return array( + array( + [], + 'eZ\\Publish\\Core\\Base\\Exceptions\\InvalidArgumentException', + ), + ); + } + + /** + * Data provider for valid input to acceptValue(). + * + * Returns an array of data provider sets with 2 arguments: 1. The valid + * input to acceptValue(), 2. The expected return value from acceptValue(). + * For example: + * + * + * return array( + * array( + * null, + * null + * ), + * array( + * __FILE__, + * new BinaryFileValue( array( + * 'path' => __FILE__, + * 'fileName' => basename( __FILE__ ), + * 'fileSize' => filesize( __FILE__ ), + * 'downloadCount' => 0, + * 'mimeType' => 'text/plain', + * ) ) + * ), + * // ... + * ); + * + * + * @return array + */ + public function provideValidInputForAcceptValue() + { + return array( + array( + null, + new Value(), + ), + array( + 1, + new Value(['rating' => 1]), + ), + array( + '2', + new Value(['rating' => 2]), + ), + ); + } + + /** + * Provide input for the toHash() method. + * + * Returns an array of data provider sets with 2 arguments: 1. The valid + * input to toHash(), 2. The expected return value from toHash(). + * For example: + * + * + * return array( + * array( + * null, + * null + * ), + * array( + * new BinaryFileValue( array( + * 'path' => 'some/file/here', + * 'fileName' => 'sindelfingen.jpg', + * 'fileSize' => 2342, + * 'downloadCount' => 0, + * 'mimeType' => 'image/jpeg', + * ) ), + * array( + * 'path' => 'some/file/here', + * 'fileName' => 'sindelfingen.jpg', + * 'fileSize' => 2342, + * 'downloadCount' => 0, + * 'mimeType' => 'image/jpeg', + * ) + * ), + * // ... + * ); + * + * + * @return array + */ + public function provideInputForToHash() + { + return array( + array( + new Value(), + ['rating' => 3], + ), + array( + new Value(['rating' => 5]), + ['rating' => 5], + ), + ); + } + + /** + * Provide input to fromHash() method. + * + * Returns an array of data provider sets with 2 arguments: 1. The valid + * input to fromHash(), 2. The expected return value from fromHash(). + * For example: + * + * + * return array( + * array( + * null, + * null + * ), + * array( + * array( + * 'path' => 'some/file/here', + * 'fileName' => 'sindelfingen.jpg', + * 'fileSize' => 2342, + * 'downloadCount' => 0, + * 'mimeType' => 'image/jpeg', + * ), + * new BinaryFileValue( array( + * 'path' => 'some/file/here', + * 'fileName' => 'sindelfingen.jpg', + * 'fileSize' => 2342, + * 'downloadCount' => 0, + * 'mimeType' => 'image/jpeg', + * ) ) + * ), + * // ... + * ); + * + * + * @return array + */ + public function provideInputForFromHash() + { + return array( + array( + array(), + new Value(array()), + ), + array( + ['rating' => 5], + new Value(['rating' => 5]), + ), + ); + } + + protected function provideFieldTypeIdentifier() + { + return 'koreRating'; + } + + public function provideDataForGetName() + { + return array( + array($this->getEmptyValueExpectation(), '3'), + array(new Value(['rating' => 5]), '5'), + ); + } +}