diff --git a/README.md b/README.md index 58a3fc3..487264b 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,8 @@ -[![Build Status](https://travis-ci.org/DevFactoryCH/taxonomy.svg?branch=master)](https://travis-ci.org/DevFactoryCH/taxonomy) -[![Latest Stable Version](https://poser.pugx.org/devfactory/taxonomy/v/stable.svg)](https://packagist.org/packages/devfactory/taxonomy) -[![Total Downloads](https://poser.pugx.org/devfactory/taxonomy/downloads.svg)](https://packagist.org/packages/devfactory/taxonomy) [![License](https://poser.pugx.org/devfactory/taxonomy/license.svg)](https://packagist.org/packages/devfactory/taxonomy) #Taxonomy -This package allows you to create vocabularies with terms in Laravel 4 and 5 +This package allows you to create vocabularies with terms in Laravel 5 ## Installation @@ -13,9 +10,16 @@ This package allows you to create vocabularies with terms in Laravel 4 and 5 In your `composer.json` add: - "require": { - "devfactory/taxonomy": "3.0.*" - } + "require": { + "tonjoo/taxonomy": "master" + } + + "repositories": [ + { + "url": "https://github.com/todiadiyatmo/taxonomy.git", + "type": "git" + } + ] From the terminal run @@ -31,11 +35,11 @@ Then register the service provider and Facade by opening `app/config/app.php` Then run the following artisant command to publish the config and migrations: - php artisan vendor:publish + php artisan vendor:publish Then run the migrations: - php artisan migrate + php artisan migrate And finally in any of the Models where you want to use the Taxonomy functionality, add the following trait: @@ -51,9 +55,9 @@ class Car extends \Eloquent { In your `composer.json` add: - "require": { - "devfactory/taxonomy": "2.0.*" - } + "require": { + "devfactory/taxonomy": "2.0.*" + } From the terminal run @@ -87,67 +91,228 @@ class Car extends \Eloquent { ## Usage -Creating a vocabulary: +Use Taxonomy base class +``` +use Devfactory\Taxonomy\Models\Term; +use Devfactory\Taxonomy\Models\TermRelation; +use Devfactory\Taxonomy\Models\Vocabulary; +``` + +### Taxonomy +`Taxonomy` is the grouping mechanism between model/object. Each group is of `taxonomy` is called `vocabulary` .The name of different group between one `vocabulary` is called `term`. The `term` can have also have parrent-child relationship. + +This goal of this package is to make create and organizing multiple taxonomy as easy as possible + +Sample : + +**Region Vocabulary** + +List of term : + +- Asia + - Indonesia + - Singapore +- Europe + - France + - Germany +- North America + - Canada + - United States of America +- Australia + - Australia + - New Zealand +- Africa + - Egypt + - Marocco + +#### Vocabulary + +**Create vocabulary** ```php -Taxonomy::createVocabulary('Cars'); +Taxonomy::createVocabulary('Region'); ``` -Retrieving a Vocabulary: - +**Retrieve vocabulary** ```php $vocabulary = Taxonomy::getVocabulary(1); // Using ID -$vocabulary = Taxonomy::getVocabularyByName('Cars'); // Using Name +$vocabulary = Taxonomy::getVocabularyByName('Region'); // Using Name +``` + +**Delete a vocabulary** +```php +$vocabulary->delete(); // Using Eloquent delete +Taxonomy::deleteVocabulary('Region'); // Using Name +``` + +#### Term + +**Adding a term to a vocabulary** +```php +$vocabulary = Taxonomy::getVocabularyByName('Region'); + +$termAsia = Taxonomy::createTerm($vocabulary->id, [ + 'name' => 'Asia', + 'description'=>'Some description ', + 'parent_id'=>0 , // optional param, set 0 if it has not parrent + 'weight'=>0, // optional param, the term can be retrieved later and sort by its weight + + ]); + +$termIndonesia = Taxonomy::createTerm($vocabulary->id, [ + 'name' => 'Indonesia', + 'description'=>'Some description', + 'parent_id'=>$termAsia->id, + + ]); +``` + +**Retrive single term** +```php +// Retrive term `Asia` from Vocabulary `Region` +$term = Taxonomy::getTerm('Region', 'Asia') + +// Other method +$vocabularyRegion = Taxonomy::getVocabularyByName('Region'); +$term = Taxonomy::getTerm($vocabularyRegion , 'Asia') +``` + +**Retrive all terms from vocabulary** +```php +/* Using Taxonomy Helper*/ + +// Get term with child +$terms = Taxonomy::getTerms('Region'); + +// Get all first level terms ( parent_id = 0 ) +$terms = Taxonomy::getTerms('Region', false); + +// Get terms from Region Vocabulary , child of Asia term +$terms = Taxonomy::getTerms('Region', 'Asia'); + +/*From vocabulary model itself*/ +$vocabularyRegion = Taxonomy::getVocabularyByName('Region'); + +// Get term with child +$terms = $vocabularyRegion->terms(); + +// Get term without child +$terms = $vocabularyRegion->terms()->where('parent_id',0)->get() ``` -Deleting a Vocabulary: +#### Working with Model + +Make sure your model is already using `\Devfactory\Taxonomy\TaxonomyTrait` +**Assign one to many relationship** ```php -Taxonomy::deleteVocabulary(1); // Using ID -Taxonomy::deleteVocabularyByName('Cars'); // Using Name +$car = \Car::findOrFail(1); + +$term = Taxonomy::getTerm('Region', 'Asia'); + +// term object +$car->setTerm($term) + +// term id +$car->setTerm(1) + ``` -Adding a Term to a vocabulary: +**Assign many to many relationship** +```php +$car = \Car::findOrFail(1); + +$car->addTerm(Taxonomy::getTerm('Region', 'Asia')); +$car->addTerm(Taxonomy::getTerm('Region', 'Europe')); +// by term id +$car->addTerm(1); +``` + +**Check if a model has a term** ```php -Taxonomy::createTerm($vocabulary->id, 'Audi'); +$car = \Car::findOrFail(1); + +$term = Taxonomy::getTerm('Region', 'Asia'); + +// by term object +$car->hasTerm($term); + +// by term id +$car->hasTerm(1); ``` -You can also optionally specify a parent term and a weight for each, so you can group them together and keep them sorted: +**Get term(s) from model** + +`getTerm` and `getTerms` will return `TermRelation` Model ```php -$german_cars = Taxonomy::createTerm($vocabulary->id, 'German Cars'); -$italian_cars = Taxonomy::createTerm($vocabulary->id, 'Italian Cars'); +$car = \Car::findOrFail(1); + +// using Vocabulary id +$termRelation = $car->getTerm(1); +$termRelations = $car->getTerms(1); + +// using Vocabulary Name +$termRelation = $car->getTerm('Region'); +$termRelations = $car->getTerms('Region'); + +$term = $termRelation->term; +$terms = $termRelations->term; -$term_audi = Taxonomy::CreateTerm($vocabulary->id, 'Audi', $german_cars->id, 0); -$term_bmw = Taxonomy::CreateTerm($vocabulary->id, 'BMW', $german_cars->id, 1); -$term_benz = Taxonomy::CreateTerm($vocabulary->id, 'Mercedes-Benz', $german_cars->id, 2); -$term_ferrari = Taxonomy::CreateTerm($vocabulary->id, 'Ferrari', $italian_cars->id, 0); ``` -With the Car Model, I can create a new instance and assign it a term for the make it belongs to: +**Remove term from model** +```php +$car = \Car::findOrFail(1); + +$term = Taxonomy::getTerm('Region', 'Asia'); + +// Remove using term object +$car->removeTerm($term); +// Remove using term id +$car->removeTerm(1); + +$car->removeTerms(); +``` + +**Remove all terms from model** ```php -$car = Car::create([ - 'model' => 'A3', -]); +$car = \Car::findOrFail(1); + +// REMOVE ALL TERMS FROM ALL VOCABULARY +$car->removeTerms(); +``` -$car->addTerm($term_bmw->id); -$car->addTerm($term_benz->id); -$car->removeAllTerms(); // Remove all terms linked to this car +**Remove all terms from specific vocabulary from model** +```php +$car = \Car::findOrFail(1); -$car->addTerm($term_ferrari->id); -$car->removeTerm($term_ferrari-id); // Remove a specific term +// Remove all term with vocabulary id = 1 +$car->removeTerms(1); -$car->addTerm($term_audi->id); +$vocabularyRegion = Taxonomy::getVocabularyByName('Region'); -// Get all the terms from the vocabulary 'Cars' That -// are attached to this Car. -$terms = $car->getTermsByVocabularyName('Cars'); +$car->removeTerms($vocabularyRegion); ``` -To retrieve all the cars that match a given term: +#### Running Query against Model + +**Get all model which belong to certain vocabulary** +```php +$vocabularyRegion = Taxonomy::getVocabulary('Region'); + +$cars = Car::whereHasVocabulary($vocabularyRegion->id)->get(); +``` +**Get all model which belong to certain term(s)** ```php -$audis = Car::getAllByTermId($term_audi->id)->get(); +$terms = Taxonomy::getTerms('Region')->pluck('id'); + +$cars = Car::whereHasTerm($terms)->get(); + +// Only Child of Asia +$terms = Taxonomy::getTerms('Region','Asia')->pluck('id'); +$cars = Car::whereHasTerm($terms)->get(); ``` diff --git a/composer.json b/composer.json index c97103f..201cfa9 100644 --- a/composer.json +++ b/composer.json @@ -1,9 +1,13 @@ { - "name": "devfactory/taxonomy", + "name": "tonjoo/taxonomy", "description": "Create and manage a heirarchical taxonomy of terms within different vocabularies", "license": "MIT", "keywords": ["taxonomy"], "authors": [ + { + "name": "Todi Adiyatmo Wijoyo", + "email": "t@tonjoo.com" + }, { "name": "Mark Cameron", "email": "mark.cameron@devfactory.ch" @@ -21,10 +25,5 @@ "psr-4": { "Devfactory\\Taxonomy\\": "src/" } - }, - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } } } diff --git a/sample.php b/sample.php new file mode 100644 index 0000000..c5cf9e1 --- /dev/null +++ b/sample.php @@ -0,0 +1,178 @@ +truncate(); + DB::table('terms')->truncate(); + DB::table('vocabularies')->truncate(); + DB::table('term_relations')->truncate(); + DB::statement("SET foreign_key_checks=1"); + + Taxonomy::createVocabulary('Region'); + + $vocabulary = Taxonomy::getVocabulary(1); + + if( $vocabulary->name === "Region") + echo "true 1
"; + + // Using ID + $vocabulary2 = Taxonomy::getVocabulary('Region'); // Using Name + + if( $vocabulary2 == $vocabulary ) + echo "true 2
"; + + $termAsia = Taxonomy::createTerm('Region', [ + 'name' => 'Asia', + 'description'=>'description', + ]); + + $termEurope = Taxonomy::createTerm('Region', [ + 'name' => 'Europe', + 'description'=>'description', + ]); + + $termIndonesia = Taxonomy::createTerm('Region', [ + 'name' => 'Indonesia', + 'description'=>'description', + 'parent_id'=>$termAsia->id + ]); + + $termSingapore = Taxonomy::createTerm('Region', [ + 'name' => 'Singapore', + 'description'=>'description', + 'parent_id'=>$termAsia->id + ]); + + + $termFrance = Taxonomy::createTerm('Region', [ + 'name' => 'France', + 'description'=>'description', + 'parent_id'=>$termEurope->id + ]); + + $termGermany = Taxonomy::createTerm('Region', [ + 'name' => 'Germany', + 'description'=>'description', + 'parent_id'=>$termEurope->id + ]); + + if( $termAsia->name === 'Asia' && $termAsia->vocabulary_id === $vocabulary->id ) + echo "true 3
"; + + if( $termEurope->name === 'Europe' && $termEurope->vocabulary_id === $vocabulary->id ) + echo "true 4
"; + + if( $termIndonesia->name === 'Indonesia' && $termIndonesia->vocabulary_id === $vocabulary->id ) + echo "true 5
"; + + if( $termSingapore->name === 'Singapore' && $termSingapore->vocabulary_id === $vocabulary->id ) + echo "true 6
"; + + if( $termFrance->name === 'France' && $termFrance->vocabulary_id === $vocabulary->id ) + echo "true 7
"; + + if( $termGermany->name === 'Germany' && $termGermany->vocabulary_id === $vocabulary->id ) + echo "true 8
"; + + $terms = Vocabulary::where('name','Region')->first()->terms; // Using Name + + if( count($terms) === 6 ) + echo "true 9
"; + + $termGetAsia = Taxonomy::getTerm('Region', 'Asia'); + + if( $termGetAsia->name == $termAsia->name ) + echo "true 10
"; + + $termsGet = Taxonomy::getTerms('Region'); + + if( count($termsGet) == count($terms) ) + echo "true 11
"; + + $listTermId = Taxonomy::getTerms('Region')->pluck('id')->toArray(); + + if( in_array($termAsia->id, $listTermId) && in_array($termEurope->id, $listTermId) && in_array($termIndonesia->id, $listTermId) ) + echo "true 12
"; + + $listTermIdAsia = Taxonomy::getTerms('Region','Asia')->pluck('id')->toArray(); + + if( in_array($termIndonesia->id, $listTermIdAsia) && in_array($termSingapore->id, $listTermIdAsia) ) + echo "true 13
"; + + + $productOne = Product::create([ + 'name'=>'Lalala', + 'price'=>1 + ]); + + $productTwo = Product::create([ + 'name'=>'Tototo', + 'price'=>2 + ]); + + + $productThree = Product::create([ + 'name'=>'Rururu', + 'price'=>2 + ]); + + + $productOne->setTerm($termIndonesia); + + // reset term + $productOne->setTerm($termAsia); + + if( @$productOne->hasTerm($termAsia) == true ) + echo "true 14
"; + + if( @$productOne->hasTerm($termIndonesia) == false ) + echo "true 15
"; + + if( @$productOne->getTerm('Region')->term->id == $termAsia->id ) + echo "true 16
"; + + // reset term + $productOne->removeTerm($termAsia); + + if( @$productOne->hasTerm($termAsia) == false ) + echo "true 17
"; + + $productOne->addTerm($termAsia); + $productOne->addTerm($termIndonesia); + $productOne->addTerm($termSingapore); + + $productTwo->addTerm($termAsia); + $productTwo->addTerm($termIndonesia); + $productTwo->addTerm($termSingapore); + + $productThree->addTerm($termAsia); + $productThree->addTerm($termIndonesia); + $productThree->addTerm($termSingapore); + + if( count($productOne->getTerms('Region')) == 3 ) + echo "true 18
"; + + + $childOfAsia = Taxonomy::getTerms('Region','Asia')->pluck('id'); + + if( count($childOfAsia) == 2 ) + echo "true 19
"; + + $productsFromTerm = Product::whereHasTerm($childOfAsia)->get(); + + if( count($productsFromTerm) == 3 ) + echo "true 20
"; + + $vocabularyRegion = Taxonomy::getVocabulary('Region'); + + $productsFromVocabulary = Product::whereHasVocabulary($vocabularyRegion->id)->get(); + + if( count($productsFromVocabulary) == 3 ) + echo "true 21
"; + +}); \ No newline at end of file diff --git a/src/Controllers/TaxonomyController.php b/src/Controllers/TaxonomyController.php index 8726da1..a056550 100644 --- a/src/Controllers/TaxonomyController.php +++ b/src/Controllers/TaxonomyController.php @@ -118,18 +118,18 @@ public function getEdit($id) { return Redirect::route($this->route_prefix . 'taxonomy.index'); } - $terms = $vocabulary->terms()->orderBy('parent', 'ASC')->orderBy('weight', 'ASC')->get(); + $terms = $vocabulary->terms()->orderBy('parent_id', 'ASC')->orderBy('weight', 'ASC')->get(); $ordered_terms = []; foreach ($terms as $term) { - if (!$term->parent) { + if (!$term->parent_id) { $ordered_terms[$term->id] = [ 'term' => $term, 'children' => [], ]; } else { - $ordered_terms[$term->parent]['children'][] = $term; + $ordered_terms[$term->parent_id]['children'][] = $term; } } @@ -147,7 +147,7 @@ public function postOrderTerms($id) { foreach ($content as $parent_key => $parent){ $parent_term = Term::find($parent->id); - $parent_term->parent = 0; + $parent_term->parent_id = 0; $parent_term->weight = $parent_key; $parent_term->save(); @@ -158,7 +158,7 @@ public function postOrderTerms($id) { foreach ($parent->children as $child_key => $child){ $child_term = Term::find($child->id); - $child_term->parent = $parent_term->id; + $child_term->parent_id = $parent_term->id; $child_term->weight = $child_key; $child_term->save(); diff --git a/src/Controllers/TermsController.php b/src/Controllers/TermsController.php index dc9bbd2..a1bb994 100644 --- a/src/Controllers/TermsController.php +++ b/src/Controllers/TermsController.php @@ -89,18 +89,18 @@ public function getEdit($id) { public function getIndex() { $vocabulary = Vocabulary::findOrFail(Input::get('id')); - $terms = $vocabulary->terms()->orderBy('parent', 'ASC')->orderBy('weight', 'ASC')->get(); + $terms = $vocabulary->terms()->orderBy('parent_id', 'ASC')->orderBy('weight', 'ASC')->get(); $ordered_terms = []; foreach ($terms as $term) { - if (!$term->parent) { + if (!$term->parent_id) { $ordered_terms[$term->id] = [ 'term' => $term, 'children' => [], ]; } else { - $ordered_terms[$term->parent]['children'][] = $term; + $ordered_terms[$term->parent_id]['children'][] = $term; } } diff --git a/src/Exceptions/TermExistsException.php b/src/Exceptions/TermExistsException.php new file mode 100644 index 0000000..f168497 --- /dev/null +++ b/src/Exceptions/TermExistsException.php @@ -0,0 +1,3 @@ + 0, + 'parent_id' => 0, + ]; + protected $fillable = [ 'name', 'vocabulary_id', - 'parent', + 'parent_id', 'weight', + 'description' ]; - public static $rules = [ - 'name' => 'required' + public static $rules = [ + 'name' => 'required' ]; + + public function termRelation() { - return $this->morphMany('Devfactory\Taxonomy\Models\TermRelation', 'relationable'); + return $this->morphMany('TermRelation', 'relationable'); } - public function vocabulary() { - return $this->belongsTo('Devfactory\Taxonomy\Models\Vocabulary'); - } + public function vocabulary() { + return $this->belongsTo('Devfactory\Taxonomy\Models\Vocabulary'); + } public function childrens() { - return $this->hasMany('Devfactory\Taxonomy\Models\Term', 'parent', 'id') - ->orderBy('weight', 'ASC'); + return $this->hasMany('Devfactory\Taxonomy\Models\Term', 'parent_id', 'id'); + } + + public function parent() { + return $this->hasOne('Devfactory\Taxonomy\Models\Term', 'id', 'parent_id'); } - public function parentTerm() { - return $this->hasOne('Devfactory\Taxonomy\Models\Term', 'id', 'parent'); + public function root() + { + return \Devfactory\Taxonomy\Facades\TaxonomyFacade::recurseRoot($this); } + + } diff --git a/src/Models/TermRelation.php b/src/Models/TermRelation.php index d42df2a..44b0a95 100644 --- a/src/Models/TermRelation.php +++ b/src/Models/TermRelation.php @@ -1,20 +1,27 @@ morphTo(); } - public function term() { - return $this->belongsTo('Devfactory\Taxonomy\Models\Term'); - } + public function vocabulary() { + return $this->belongsTo('Devfactory\Taxonomy\Models\Vocabulary'); + } + + + public function term() { + return $this->belongsTo('Devfactory\Taxonomy\Models\Term'); + } } diff --git a/src/Models/Vocabulary.php b/src/Models/Vocabulary.php index 196e6a6..1e2a297 100644 --- a/src/Models/Vocabulary.php +++ b/src/Models/Vocabulary.php @@ -1,7 +1,7 @@ vocabulary = $vocabulary; $this->term = $term; @@ -25,13 +27,15 @@ public function __construct(Vocabulary $vocabulary, Term $term) { * The Vocabulary object if created, FALSE if error creating, * Exception if the vocabulary name already exists. */ - public function createVocabulary($name) { - if ($this->vocabulary->where('name', $name)->count()) { + public function createVocabulary($name) + { + if ($this->vocabulary->where('name', $name)->count()) + { throw new Exceptions\VocabularyExistsException(); } - return $this->vocabulary->create(['name' => $name]); - } + return $this->vocabulary->create(['name' => $name]); + } /** * Get a Vocabulary by ID @@ -42,12 +46,37 @@ public function createVocabulary($name) { * @return * The Vocabulary Model object, otherwise NULL */ - public function getVocabulary($id) { - return $this->vocabulary->find($id); + public function getVocabulary($vocabulary) + { + if($vocabulary instanceof Vocabulary) + return $vocabulary; + elseif(is_string($vocabulary)) + return $this->vocabulary->where('name', $vocabulary)->first(); + elseif(is_numeric($vocabulary)) + return $this->vocabulary->where('id', $vocabulary)->first(); + + return false; } /** - * Get a Vocabulary by name + * Get single term by name + * + * @param string $name + * The name of the Vocabulary to fetch + * + * @return + * The Vocabulary Model object, otherwise NULL + */ + public function getTerm( $vocabulary, $name ) + { + + $vocabulary = $this->getVocabulary($vocabulary); + + return $this->term->where( 'vocabulary_id', $vocabulary->id )->where( 'name', $name )->first(); + } + + /** + * Get a terms * * @param string $name * The name of the Vocabulary to fetch @@ -55,8 +84,30 @@ public function getVocabulary($id) { * @return * The Vocabulary Model object, otherwise NULL */ - public function getVocabularyByName($name) { - return $this->vocabulary->where('name', $name)->first(); + public function getTerms($vocabulary, $parentName = true, $withParent = false) + { + $vocabulary = $this->getVocabulary($vocabulary); + + if(!$vocabulary) + return collect([]); + + if( $parentName === true ) + return $vocabulary->terms; + + + if( $parentName ) + { + $term = $this->getTerm($vocabulary, $parentName); + + $terms = $vocabulary->terms()->where($this->term->getTable().'.parent_id','=', $term->id)->get(); + + if( $withParent ) + return $terms->push($term); + + return $terms; + } + + return collect([]); } /** @@ -68,8 +119,9 @@ public function getVocabularyByName($name) { * @return * The Vocabulary Model object, otherwise NULL */ - public function getVocabularyByNameAsArray($name) { - $vocabulary = $this->vocabulary->where('name', $name)->first(); + public function getTermsByNameAsArray( $vocabulary , $field='name' ) + { + $vocabulary = $this->getVocabulary($vocabulary); if (!is_null($vocabulary)) { return $vocabulary->terms->lists('name', 'id')->toArray(); @@ -78,6 +130,8 @@ public function getVocabularyByNameAsArray($name) { return []; } + + /** * Get a Vocabulary by name as an options array for dropdowns * @@ -87,11 +141,12 @@ public function getVocabularyByNameAsArray($name) { * @return * The Vocabulary Model object, otherwise NULL */ - public function getVocabularyByNameOptionsArray($name) { + public function getVocabularyByNameOptionsArray($name) + { $vocabulary = $this->vocabulary->where('name', $name)->first(); if (is_null($vocabulary)) { - return []; + return collect([]); } $parents = $this->term->whereParent(0) @@ -117,7 +172,8 @@ public function getVocabularyByNameOptionsArray($name) { * * @return array */ - private function recurse_children($parent, &$options, $depth = 1) { + private function recurse_children($parent, &$options, $depth = 1) + { $parent->childrens->map(function($child) use (&$options, $depth) { $options[$child->id] = str_repeat('-', $depth) .' '. $child->name; @@ -127,23 +183,22 @@ private function recurse_children($parent, &$options, $depth = 1) { }); } - /** - * Delete a Vocabulary by ID - * - * @param int $id - * The ID of the Vocabulary to delete - * - * @return bool - * TRUE if Vocabulary is deletes, otherwise FALSE - * - * @thrown Illuminate\Database\Eloquent\ModelNotFoundException - */ - public function deleteVocabulary($id) { - $vocabulary = $this->vocabulary->findOrFail($id); - return $vocabulary->delete(); + public function recurseRoot( $term ) + { + + if($term->parent_id == 0 ) + return $term; + + if($term->parent) + { + return $this->recurseRoot($term->parent); + } + } + + /** * Delete a Vocabulary by ID * @@ -155,16 +210,23 @@ public function deleteVocabulary($id) { * * @thrown Illuminate\Database\Eloquent\ModelNotFoundException */ - public function deleteVocabularyByName($name) { - $vocabulary = $this->vocabulary->where('name', $name)->first(); + public function deleteVocabulary($vocabulary) { - if (!is_null($vocabulary)) { + if( is_string( $vocabulary ) ) + { + + $vocabulary = $this->getVocabulary( $vocabulary ); + + if( !$vocabulary ) + return false; + return $vocabulary->delete(); } - return FALSE; + return false; } + /** * Create a new term in a specific vocabulary * @@ -185,15 +247,29 @@ public function deleteVocabularyByName($name) { * * @thrown Illuminate\Database\Eloquent\ModelNotFoundException */ - public function createTerm($vid, $name, $parent = 0, $weight = 0) { - $vocabulary = $this->vocabulary->findOrFail($vid); - - $term = [ - 'name' => $name, - 'vocabulary_id' => $vid, - 'parent' => $parent, - 'weight' => $weight, - ]; + public function createTerm($vocabulary, $term = array() ) + { + $vocabulary = $this->getVocabulary($vocabulary); + + if(!$vocabulary) + { + throw new Exceptions\VocabularyNotExistsException(); + } + + + // if($this->vocabulary->terms) + + $term['vocabulary_id'] = $vocabulary->id; + $term['parent_id'] = isset($term['parent_id']) ? $term['parent_id'] : 0 ; + $term['weight'] = isset($term['weight']) ? $term['weight'] : 0 ; + + if($vocabulary->terms() + ->where($this->term->getTable().'.parent_id', $term['parent_id']) + ->where($this->term->getTable().'.name', $term['name']) + ->first()) + { + throw new Exceptions\TermExistsException(); + } return $this->term->create($term); } diff --git a/src/TaxonomyTrait.php b/src/TaxonomyTrait.php index 3faa802..313cc2d 100644 --- a/src/TaxonomyTrait.php +++ b/src/TaxonomyTrait.php @@ -2,20 +2,105 @@ use Devfactory\Taxonomy\Models\TermRelation; use Devfactory\Taxonomy\Models\Term; +use Devfactory\Taxonomy\Models\Vocabulary; trait TaxonomyTrait { /** - * Return collection of tags related to the tagged model - * - * @return Illuminate\Database\Eloquent\Collection - */ - public function related() { - return $this->morphMany('Devfactory\Taxonomy\Models\TermRelation', 'relationable'); - } + * Get related vocabulary + */ + + public function getTaxonomiesAttribute() + { + $relatedGrouped = $this->related()->get()->groupBy('vocabulary_id'); + + $groupedTaxonomy = []; + + foreach ($relatedGrouped as $key => $relateds) { + + $vocabulary = Vocabulary::find($key); + + $slug = str_slug($vocabulary->name,'_'); + + $groupedTaxonomy[$slug] = []; + // $groupedTaxonomy[$key]['name'] = $vocabulary->name; + // $groupedTaxonomy[$key]['id'] = $vocabulary->id; + + // $groupedTaxonomy[$key]['terms'] = []; + + foreach ($relateds as $related) { + + $term = $related->term; + + $termData = [ + 'id' => $term->id, + 'name' => $term->name, + 'description' => $term->description, + 'root_term_id' => false, + 'root_term_name' => false, + 'root_term_description' => false + ]; + + $root = $term->root(); + + if( $root->id != $term->id ) + { + $termData['root_term_id'] = $root->id; + $termData['root_term_name'] = $root->name; + $termData['root_term_description'] = $root->description; + } + + array_push( $groupedTaxonomy[$slug], $termData ); + } + + } + + return $this->attributes['taxonomies'] = $groupedTaxonomy; + } + /** + * Return collection of tags related to the tagged model + * + * @return Illuminate\Database\Eloquent\Collection + */ + public function related() { + return $this->morphMany('Devfactory\Taxonomy\Models\TermRelation', 'relationable'); + } + + /** + * Set term to the inheriting model ( One to many ) + * + * @param $term_id int + * The ID of the term or an instance of the Term object + * + * @return object + * The TermRelation object + */ + public function setTerm($term, $description="") { + + $term = ($term instanceof Term) ? $term : Term::findOrFail($term); + + if(!$term) + return; + + if( $this->hasTerm($term) ) + return $this->updateTerm($term,$description); + + // Check if the new term is already there + // $related = $this->related()->first(); + + // if( $related && $related->term->id == $term->id ) + // { + // return $this->updateTerm($term, $description); + // } + + // Remove all term from same vocabulary + $this->related()->where('relationable_id', $this->id)->where('vocabulary_id',$term->vocabulary->id)->delete(); + + return $this->addTerm($term,$description); + } /** - * Add an existing term to the inheriting model + * Add an existing term to the inheriting model (Many to Many ) * * @param $term_id int * The ID of the term or an instance of the Term object @@ -23,17 +108,50 @@ public function related() { * @return object * The TermRelation object */ - public function addTerm($term_id) { - $term = ($term_id instanceof Term) ? $term_id : Term::findOrFail($term_id); + public function addTerm($term, $description="") { + + $term = ($term instanceof Term) ? $term : Term::findOrFail($term); + + if(!$term) + return; + + if( $this->hasTerm($term) ) + return $this->updateTerm($term,$description); $term_relation = [ - 'term_id' => $term->id, - 'vocabulary_id' => $term->vocabulary_id, + 'term_id' => $term->id, + 'vocabulary_id' => $term->vocabulary_id, + 'description' => $description, ]; - $this->related()->save(new TermRelation($term_relation)); + return $this->related()->save(new TermRelation($term_relation)); } + + + /** + * Update an existing term to the inheriting model + * + * @param $term_id int + * The ID of the term or an instance of the Term object + * + * @return object + * The TermRelation object + */ + public function updateTerm($term,$description="") { + $term = ($term instanceof Term) ? $term : Term::findOrFail($term); + + if(!$term) + return; + + if( !$this->related() ) + return; + + $this->related()->where('term_id',$term->id)->update(['description' => $description]); + } + + + /** * Check if the Model instance has the passed term as an existing relation * @@ -43,19 +161,42 @@ public function addTerm($term_id) { * @return object * The TermRelation object */ - public function hasTerm($term_id) { - $term = ($term_id instanceof Term) ? $term_id : Term::findOrFail($term_id); + public function hasTerm($term) { + $term = ($term instanceof Term) ? $term : Term::findOrFail($term); + if(!$term) + return false; + return ($this->related()->where('term_id', $term->id )->count()) ? TRUE : FALSE; + } - $term_relation = [ - 'term_id' => $term->id, - 'vocabulary_id' => $term->vocabulary_id, - ]; + /** + * Check if the Model instance has the passed term as an existing relation + * + * @param mixed $term_id + * The ID of the term or an instance of the Term object + * + * @return object + * The TermRelation object + */ + public function hasVocabulary($vocabulary, $name=false) { + + $vocabulary = \Devfactory\Taxonomy\Facades\TaxonomyFacade::getVocabulary($vocabulary); + + if(!$vocabulary) + return false; + + if(!$name) + return $this->related()->where('term_relations.vocabulary_id', $vocabulary->id)->first(); - return ($this->related()->where('term_id', $term_id)->count()) ? TRUE : FALSE; + return $this ->related() + ->where('term_relations.vocabulary_id', $vocabulary->id) + ->whereHas('term', function($q) use($name) { + $q->where('term.name', '=', $name ); + }) + ->first(); } /** - * Get all the terms for a given vocabulary that are linked to the current + * Get all the term relations for a given vocabulary that are linked to the current * Model. * * @param $name string @@ -64,34 +205,63 @@ public function hasTerm($term_id) { * @return object * A collection of TermRelation objects */ - public function getTermsByVocabularyName($name) { - $vocabulary = \Taxonomy::getVocabularyByName($name); + public function getRelated($term,$name=false) { + + $term = ($term instanceof Term) ? $term : Term::findOrFail($term); + if(!$term) + return false; - return $this->related()->where('vocabulary_id', $vocabulary->id)->get(); + return $this->related()->where('term_id', $term->id )->first(); } /** - * Get all the terms for a given vocabulary that are linked to the current - * Model as a key value pair array. + * Get all the term relations for a given vocabulary that are linked to the current + * Model. * * @param $name string * The name of the vocabulary * - * @return array - * A key value pair array of the type 'id' => 'name' + * @return object + * A collection of TermRelation objects */ - public function getTermsByVocabularyNameAsArray($name) { - $vocabulary = \Taxonomy::getVocabularyByName($name); + public function getTerm($vocabulary,$name=false) { - $term_relations = $this->related()->where('vocabulary_id', $vocabulary->id)->get(); + $vocabulary = \Devfactory\Taxonomy\Facades\TaxonomyFacade::getVocabulary($vocabulary); - $data = []; - foreach ($term_relations as $term_relation) { - $data[$term_relation->term->id] = $term_relation->term->name; - } + if(!$vocabulary) + return false; + + if(!$name) + return $this->related()->where('term_relations.vocabulary_id', $vocabulary->id)->first(); - return $data; + return $this ->related() + ->where('term_relations.vocabulary_id', $vocabulary->id) + ->whereHas('term', function($q) use($name) { + $q->where('term.name', '=', $name ); + }) + ->first(); } + /** + * Get all the terms for a given vocabulary that are linked to the current + * Model. + * + * @param $name string + * The name of the vocabulary + * + * @return object + * A collection of TermRelation objects + */ + public function getTerms($vocabulary) { + + $vocabulary = \Devfactory\Taxonomy\Facades\TaxonomyFacade::getVocabulary($vocabulary); + + if(!$vocabulary) + return; + + return $this->related()->where('term_relations.vocabulary_id', $vocabulary->id)->get(); + } + + /** * Unlink the given term with the current model object @@ -102,8 +272,9 @@ public function getTermsByVocabularyNameAsArray($name) { * @return bool * TRUE if the term relation has been deleted, otherwise FALSE */ - public function removeTerm($term_id) { - $term_id = ($term_id instanceof Term) ? $term_id->id : $term_id; + public function removeTerm($term) { + $term_id = ($term instanceof Term) ? $term->id : $term; + return $this->related()->where('term_id', $term_id)->delete(); } @@ -113,8 +284,15 @@ public function removeTerm($term_id) { * @return bool * TRUE if the term relation has been deleted, otherwise FALSE */ - public function removeAllTerms() { - return $this->related()->delete(); + public function removeTerms($vocabulary=false) { + + $vocabulary = \Devfactory\Taxonomy\Facades\TaxonomyFacade::getVocabulary($vocabulary); + + if(!$vocabulary) + return false; + + return $this->related()->where('vocabulary_id', $vocabulary->id )->delete(); + } /** @@ -125,14 +303,61 @@ public function removeAllTerms() { * * @return void */ - public function scopeGetAllByTermId($query, $term_id) { - return $query->whereHas('related', function($q) use($term_id) { - if (is_array($term_id)) { - $q->whereIn('term_id', $term_id); - } - else { - $q->where('term_id', '=', $term_id); - } - }); + public function scopeWhereHasTerm($query, $term_id) + { + + + if( !is_array($term_id) ) + { + return $query->whereHas('related', function($q) use($term_id) + { + $q->where('term_id', '=', $term_id ); + }); + } + + if( method_exists($term_id,'toArray') ) + $term_id = $term_id->toArray(); + + foreach ($term_id as $single_term_id) + { + $query = $query->whereHas('related', function($q) use($single_term_id) + { + $q->where('term_id', '=', $single_term_id ); + }); + } + + return $query; + } + + /** + * Filter the model to return a subset of entries matching the term ID + * + * @param object $query + * @param int $term_id + * + * @return void + */ + public function scopeWhereHasVocabulary($query, $vocabulary_id) + { + if( !is_array($vocabulary_id) ) + { + return $query->whereHas('related', function($q) use($vocabulary_id) + { + $q->where('vocabulary_id', '=', $vocabulary_id ); + }); + } + + if( method_exists($vocabulary_id,'toArray') ) + $vocabulary_id = $vocabulary_id->toArray(); + + foreach ($vocabulary_id as $single_vocabulary_id) + { + $query = $query->whereHas('related', function($q) use($single_vocabulary_id) + { + $q->where('vocabulary_id', '=', $single_vocabulary_id ); + }); + } + + return $query; } } diff --git a/src/migrations/2014_10_29_164925_create_terms_table.php b/src/migrations/2014_10_29_164925_create_terms_table.php index 9d0d995..fe3078d 100644 --- a/src/migrations/2014_10_29_164925_create_terms_table.php +++ b/src/migrations/2014_10_29_164925_create_terms_table.php @@ -16,8 +16,9 @@ public function up() $table->integer('vocabulary_id')->unsigned(); $table->foreign('vocabulary_id')->references('id')->on('vocabularies')->onDelete('cascade'); $table->string('name'); - $table->integer('parent')->unsigned(); - $table->integer('weight'); + $table->integer('parent_id')->unsigned(); + $table->unique(['parent_id','name']); + $table->integer('weight'); $table->timestamps(); }); } diff --git a/src/migrations/2015_03_21_164940_add_description_column.php b/src/migrations/2015_03_21_164940_add_description_column.php new file mode 100644 index 0000000..a998e54 --- /dev/null +++ b/src/migrations/2015_03_21_164940_add_description_column.php @@ -0,0 +1,43 @@ +string('description',1000); + }); + + // + Schema::table('term_relations', function ($table) { + $table->string('description',1000); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + Schema::table('terms', function ($table) { + $table->dropColumn('description'); + }); + + // + Schema::table('term_relations', function ($table) { + $table->dropColumn('description'); + }); + } + +} \ No newline at end of file