diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 61b0c9f..de09355 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -6,4 +6,4 @@ checks: duplication: true filter: - paths: [code/*, tests/*] + paths: [src/*, tests/*] diff --git a/.travis.yml b/.travis.yml index 02667b6..5a5a095 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,26 +4,18 @@ sudo: false language: php -php: - - 5.3 - - 5.4 - - 5.5 - - 5.6 - - 7.0 - -env: - - DB=MYSQL CORE_RELEASE=3.2 - matrix: include: + - php: 5.5 + env: DB=MYSQL CORE_RELEASE=4 - php: 5.6 - env: DB=MYSQL CORE_RELEASE=3 + env: DB=MYSQL CORE_RELEASE=4 - php: 5.6 - env: DB=MYSQL CORE_RELEASE=3.1 - - php: 5.6 - env: DB=PGSQL CORE_RELEASE=3.2 - allow_failures: + env: DB=PGSQL CORE_RELEASE=4 - php: 7.0 + env: DB=MYSQL CORE_RELEASE=4 + - php: 7.0 + env: DB=PGSQL CORE_RELEASE=4 before_script: - composer self-update || true diff --git a/composer.json b/composer.json index 1a02d62..7f49a93 100644 --- a/composer.json +++ b/composer.json @@ -1,29 +1,34 @@ { - "name": "silverstripe/tagfield", - "description": "Tag field for Silverstripe", - "license": "BSD-3-Clause", - "type": "silverstripe-module", - "keywords": [ - "silverstripe", - "tag", - "field" - ], - "authors": [ - { - "name": "Christopher Pitt", - "email": "chris@silverstripe.com", - "homepage": "http://github.com/assertchris" - } - ], - "support": { - "issues": "http://github.com/silverstripe-labs/silverstripe-tagfield/issues" - }, - "require": { - "silverstripe/framework": "~3.1" - }, - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - } + "name": "silverstripe/tagfield", + "description": "Tag field for Silverstripe", + "license": "BSD-3-Clause", + "type": "silverstripe-module", + "keywords": [ + "silverstripe", + "tag", + "field" + ], + "authors": [ + { + "name": "Christopher Pitt", + "email": "chris@silverstripe.com", + "homepage": "http://github.com/assertchris" + } + ], + "support": { + "issues": "http://github.com/silverstripe-labs/silverstripe-tagfield/issues" + }, + "require": { + "silverstripe/framework": "^4.0" + }, + "autoload": { + "psr-4": { + "SilverStripe\\TagField\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + } } diff --git a/css/TagField.css b/css/TagField.css index 7cd2636..0f0b3e5 100644 --- a/css/TagField.css +++ b/css/TagField.css @@ -50,7 +50,7 @@ } .select2-selection__choice { - height: 13px; + height: 22px; line-height: 13px; -webkit-border-radius: 3px; -moz-border-radius: 3px; @@ -100,4 +100,4 @@ background-image: -ms-linear-gradient(top, #3875d7 20%, #2a62bc 90%) !important; background-image: linear-gradient(top, #3875d7 20%, #2a62bc 90%) !important; color: #fff !important; -} \ No newline at end of file +} diff --git a/docs/en/using.md b/docs/en/using.md index 12890e5..9aa1d4f 100644 --- a/docs/en/using.md +++ b/docs/en/using.md @@ -3,19 +3,21 @@ The primary use, for this module, is as a custom input field interface. For instance, imagine you had the following data objects: ```php -class BlogPost extends DataObject { +class BlogPost extends DataObject +{ private static $many_many = array( - 'BlogTags' => 'BlogTag' + 'BlogTags' => 'SilverStripe\\Blog\\Model\\BlogTag' ); } -class BlogTag extends DataObject { +class BlogTag extends DataObject +{ private static $db = array( - 'Title' => 'Varchar(200)', + 'Title' => 'Varchar(200)' ); private static $belongs_many_many = array( - 'BlogPosts' => 'BlogPost' + 'BlogPosts' => 'SilverStripe\\Blog\\Model\\BlogPost' ); } ``` @@ -33,6 +35,8 @@ $field = TagField::create( ->setCanCreate(true); // new tag DataObjects can be created ``` +**Note:** This assumes you have imported the namespaces class, e.g. `use SilverStripe\TagField\TagField;`. + This will present a tag field, in which you can select existing blog tags or create new ones. They will be created/linked after the blog posts are saved. You can also store string-based tags, for blog posts, with the following field type: @@ -51,7 +55,8 @@ $field->setShouldLazyLoad(true); // tags should be lazy loaded This assumes you are storing tags in the following data object structure: ```php -class BlogPost extends DataObject { +class BlogPost extends DataObject +{ private static $db = array( 'Tags' => 'Text' ); diff --git a/js/TagField.js b/js/TagField.js index 49e9611..98b2cb4 100644 --- a/js/TagField.js +++ b/js/TagField.js @@ -10,11 +10,11 @@ */ $.fn.chosenDestroy = function () { var $this = $(this); - if ($this.siblings('.chzn-container').length) { + if ($this.siblings('.chosen-container').length) { $this .show() // The field needs to be visible so Select2 evaluates the width correctly. - .removeClass('chzn-done') - .removeClass('has-chzn') + .removeClass('chosen-done') + .removeClass('has-chosen') .next() .remove(); } @@ -23,7 +23,7 @@ $.entwine('ss', function ($) { - $('.ss-tag-field.has-chzn + .chzn-container, .ss-tag-field:not(.has-chzn)').entwine({ + $('.ss-tag-field.has-chosen + .chosen-container, .ss-tag-field:not(.has-chosen)').entwine({ applySelect2: function () { var self = this, $select = $(this); diff --git a/src/.upgrade.yml b/src/.upgrade.yml new file mode 100644 index 0000000..d13de71 --- /dev/null +++ b/src/.upgrade.yml @@ -0,0 +1,4 @@ +mappings: + StringTagField: SilverStripe\TagField\StringTagField + TagField: SilverStripe\TagField\TagField + TagField_Readonly: SilverStripe\TagField\TagField\Readonly diff --git a/code/StringTagField.php b/src/StringTagField.php similarity index 89% rename from code/StringTagField.php rename to src/StringTagField.php index 83a0292..8c1cd22 100644 --- a/code/StringTagField.php +++ b/src/StringTagField.php @@ -1,12 +1,26 @@ customise($properties) - ->renderWith(array("templates/TagField")); + ->renderWith(TagField::class); } /** @@ -264,17 +267,16 @@ class StringTagField extends DropdownField /** * Returns a JSON string of tags, for lazy loading. * - * @param SS_HTTPRequest $request - * - * @return SS_HTTPResponse + * @param HTTPRequest $request + * @return HTTPResponse */ - public function suggest(SS_HTTPRequest $request) + public function suggest(HTTPRequest $request) { $responseBody = Convert::raw2json( array('items' => array()) ); - $response = new SS_HTTPResponse(); + $response = new HTTPResponse; $response->addHeader('Content-Type', 'application/json'); if ($record = $this->getRecord()) { diff --git a/code/TagField.php b/src/TagField.php similarity index 79% rename from code/TagField.php rename to src/TagField.php index 7fd43a5..39ac447 100644 --- a/code/TagField.php +++ b/src/TagField.php @@ -1,5 +1,21 @@ setSourceList($source); parent::__construct($name, $title, $source, $value); } @@ -151,6 +173,26 @@ class TagField extends DropdownField return $this; } + /** + * Get the DataList source. The 4.x upgrade for SelectField::setSource starts to convert this to an array + * @return DataList + */ + public function getSourceList() + { + return $this->sourceList; + } + + /** + * Set the model class name for tags + * @param DataList $className + * @return self + */ + public function setSourceList($sourceList) + { + $this->sourceList = $sourceList; + return $this; + } + /** * {@inheritdoc} */ @@ -159,8 +201,8 @@ class TagField extends DropdownField Requirements::css(TAG_FIELD_DIR . '/css/select2.min.css'); Requirements::css(TAG_FIELD_DIR . '/css/TagField.css'); - Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js'); - Requirements::javascript(THIRDPARTY_DIR . '/jquery-entwine/dist/jquery.entwine-dist.js'); + Requirements::javascript(ADMIN_THIRDPARTY_DIR . '/jquery/jquery.js'); + Requirements::javascript(ADMIN_THIRDPARTY_DIR . '/jquery-entwine/dist/jquery.entwine-dist.js'); Requirements::javascript(TAG_FIELD_DIR . '/js/select2.js'); Requirements::javascript(TAG_FIELD_DIR . '/js/TagField.js'); @@ -182,7 +224,7 @@ class TagField extends DropdownField return $this ->customise($properties) - ->renderWith(array("templates/TagField")); + ->renderWith(self::class); } /** @@ -200,21 +242,21 @@ class TagField extends DropdownField { $options = ArrayList::create(); - $source = $this->getSource(); + $source = $this->getSourceList(); if(!$source) { - $source = new ArrayList(); + $source = ArrayList::create(); } $dataClass = $source->dataClass(); $values = $this->Value(); - if(!$values) { + if (!$values) { return $options; } - if(is_array($values)) { + if (is_array($values)) { $values = DataList::create($dataClass)->filter('Title', $values); } @@ -276,26 +318,21 @@ class TagField extends DropdownField parent::saveInto($record); $name = $this->getName(); - $titleField = $this->getTitleField(); - $source = $this->getSource(); - $values = $this->Value(); - $relation = $record->$name(); - $ids = array(); - if(!$values) { + if (!$values) { $values = array(); } - if(empty($record) || empty($source) || empty($titleField)) { + if (empty($record) || empty($source) || empty($titleField)) { return; } - if(!$record->hasMethod($name)) { + if (!$record->hasMethod($name)) { throw new Exception( sprintf("%s does not have a %s method", get_class($record), $name) ); @@ -304,31 +341,31 @@ class TagField extends DropdownField foreach ($values as $key => $value) { // Get or create record $record = $this->getOrCreateTag($value); - if($record) { + if ($record) { $ids[] = $record->ID; $values[$key] = $record->Title; } } $relation->setByIDList(array_filter($ids)); - } /** * Get or create tag with the given value * - * @param string $term + * @param string $term * @return DataObject */ protected function getOrCreateTag($term) { // Check if existing record can be found - $source = $this->getSource(); + /** @var DataList $source */ + $source = $this->getSourceList(); $titleField = $this->getTitleField(); $record = $source ->filter($titleField, $term) ->first(); - if($record) { + if ($record) { return $record; } @@ -347,15 +384,14 @@ class TagField extends DropdownField /** * Returns a JSON string of tags, for lazy loading. * - * @param SS_HTTPRequest $request - * - * @return SS_HTTPResponse + * @param HTTPRequest $request + * @return HTTPResponse */ - public function suggest(SS_HTTPRequest $request) + public function suggest(HTTPRequest $request) { $tags = $this->getTags($request->getVar('term')); - $response = new SS_HTTPResponse(); + $response = new HTTPResponse(); $response->addHeader('Content-Type', 'application/json'); $response->setBody(json_encode(array('items' => $tags))); @@ -365,16 +401,15 @@ class TagField extends DropdownField /** * Returns array of arrays representing tags. * - * @param string $term - * + * @param string $term * @return array */ protected function getTags($term) { /** - * @var DataList $source + * @var array $source */ - $source = $this->getSource(); + $source = $this->getSourceList(); $titleField = $this->getTitleField(); @@ -415,40 +450,8 @@ class TagField extends DropdownField */ public function performReadonlyTransformation() { - $copy = $this->castedCopy('TagField_Readonly'); - $copy->setSource($this->getSource()); + $copy = $this->castedCopy(TagFieldReadonly::class); + $copy->setSourceList($this->getSourceList()); return $copy; } } - -/** - * A readonly extension of TagField useful for non-editable items within the CMS. - * - * @package forms - * @subpackage fields - */ -class TagField_Readonly extends TagField -{ - protected $readonly = true; - - /** - * Render the readonly field as HTML. - * - * @param array $properties - * @return HTMLText - */ - public function Field($properties = array()) - { - $options = array(); - - foreach ($this->getOptions()->filter('Selected', true) as $option) { - $options[] = $option->Title; - } - - $field = ReadonlyField::create($this->name.'_Readonly', $this->title); - - $field->setForm($this->form); - $field->setValue(implode(', ', $options)); - return $field->Field(); - } -} diff --git a/src/TagField/Readonly.php b/src/TagField/Readonly.php new file mode 100644 index 0000000..183f0b0 --- /dev/null +++ b/src/TagField/Readonly.php @@ -0,0 +1,41 @@ +getOptions()->filter('Selected', true) as $option) { + $options[] = $option->Title; + } + + $field = ReadonlyField::create($this->name . '_Readonly', $this->title); + + $field->setForm($this->form); + $field->setValue(implode(', ', $options)); + return $field->Field(); + } +} diff --git a/templates/TagField.ss b/templates/SilverStripe/TagField/TagField.ss similarity index 100% rename from templates/TagField.ss rename to templates/SilverStripe/TagField/TagField.ss diff --git a/tests/StringTagFieldTest.php b/tests/StringTagFieldTest.php index b3149fa..7f39e70 100755 --- a/tests/StringTagFieldTest.php +++ b/tests/StringTagFieldTest.php @@ -1,5 +1,15 @@ getNewTagFieldTestBlogPost('BlogPost1'); - $field = new TagField('Tags', '', new DataList('TagFieldTestBlogTag')); $field->setValue(array('Tag3', 'Tag4')); $field->saveInto($record); - $record->write(); - $this->compareExpectedAndActualTags( array('Tag3', 'Tag4'), $record @@ -65,7 +73,6 @@ class TagFieldTest extends SapphireTest protected function compareTagLists(array $expected, DataList $actualSource) { $actual = array_values($actualSource->map('ID', 'Title')->toArray()); - sort($expected); sort($actual); @@ -247,11 +254,11 @@ class TagFieldTest extends SapphireTest /** * @param array $parameters * - * @return SS_HTTPRequest + * @return HTTPRequest */ protected function getNewRequest(array $parameters) { - return new SS_HTTPRequest( + return new HTTPRequest( 'get', 'TagFieldTestController/TagFieldTestForm/fields/Tags/suggest', $parameters @@ -283,9 +290,8 @@ class TagFieldTest extends SapphireTest public function testItIgnoresNewTagsIfCannotCreate() { - $this->markTestSkipped( - 'This test has not been updated yet.' + 'This test has not been updated yet.' ); $record = new TagFieldTestBlogPost(); @@ -320,14 +326,14 @@ class TagFieldTestBlogTag extends DataObject implements TestOnly * @var array */ private static $db = array( - 'Title' => 'Varchar(200)', + 'Title' => 'Varchar(200)' ); /** * @var array */ private static $belongs_many_many = array( - 'BlogPosts' => 'TagFieldTestBlogPost', + 'BlogPosts' => 'TagFieldTestBlogPost' ); } @@ -340,15 +346,15 @@ class TagFieldTestBlogPost extends DataObject implements TestOnly * @var array */ private static $db = array( - 'Title' => 'Text', - 'Content' => 'Text', + 'Title' => 'Text', + 'Content' => 'Text' ); /** * @var array */ private static $many_many = array( - 'Tags' => 'TagFieldTestBlogTag', + 'Tags' => 'TagFieldTestBlogTag' ); }