Codeigniter add multiple category to a course (Academy-LMS) - php

I am using Academy LMS Source code is in Github
it's a Learning Management system and it is designed to have one category per course
I want to add multiple categories to one course
In /controllers/API.php I have
// Fetch all the categories
public function categories_get($category_id = "") {
$categories = array();
$categories = $this->api_model->categories_get($category_id);
$this->set_response($categories, REST_Controller::HTTP_OK);
}
// Fetch all the courses belong to a certain category
public function category_wise_course_get() {
$category_id = $_GET['category_id'];
$courses = $this->api_model->category_wise_course_get($category_id);
$this->set_response($courses, REST_Controller::HTTP_OK);
}
then in /models/Api_model.php, I have
// Get categories
public function categories_get($category_id)
{
if ($category_id != "") {
$this->db->where('id', $category_id);
}
$this->db->where('parent', 0);
$categories = $this->db->get('category')->result_array();
foreach ($categories as $key => $category) {
$categories[$key]['thumbnail'] = $this->get_image('category_thumbnail', $category['thumbnail']);
$categories[$key]['number_of_courses'] = $this->crud_model->get_category_wise_courses($category['id'])->num_rows();
}
return $categories;
}
// Get category wise courses
public function category_wise_course_get($category_id)
{
$category_details = $this->crud_model->get_category_details_by_id($category_id)->row_array();
if ($category_details['parent'] > 0) {
$this->db->where('sub_category_id', $category_id);
} else {
$this->db->where('category_id', $category_id);
}
$this->db->where('status', 'active');
$courses = $this->db->get('course')->result_array();
// This block of codes return the required data of courses
$result = array();
$result = $this->course_data($courses);
return $result;
}
then in /model/Crud_model.php, I got
public function get_category_details_by_id($id)
{
return $this->db->get_where('category', array('id' => $id));
}
function get_category_wise_courses($category_id = "")
{
$category_details = $this->get_category_details_by_id($category_id)->row_array();
if ($category_details['parent'] > 0) {
$this->db->where('sub_category_id', $category_id);
} else {
$this->db->where('category_id', $category_id);
}
$this->db->where('status', 'active');
return $this->db->get('course');
}
in SQL course table has a column named category_id int(11) which stores one category per course I've changed it to TEXT format and put comma-separated values like 1,2,3 and used the ways like
$this->db->where_in('category_id',$category_id)
and
$this->db->like('category_id',$category_id)
and
$this->db->where("FIND_IN_SET(".$category_id.",category_id) >", 0);
and got no result I just need courses to have comma-separated values in the category_id column
Simply, I want courses to have multiple categories
DB tables
https://pasteboard.co/JNSxO2kz.png
course table
https://pasteboard.co/JNSy7g0.png
category table
https://pasteboard.co/JNSyhlk.png
category_id table (Mapping Table suggested by #micheal-la-ferla )
https://pasteboard.co/JNSyGGn.png
anybody could help?

The way Academy-LMS is designed, it lets you only add 1 category per course. It limits the user and it can be frustrating.
One workaround which could work is to create a new mapping table with 3 columns as follows:
Field Name | Data Type
------------+------------------
ID | int(11)
Course ID | int(11)
Category ID | int(11)
Obviously you need to have the permission to create new tables to use this workaround. Not sure if this is possible for your implementation of Academy-LMS.
The step above will essentially create a one to many relationship between the course and the category, so that 1 course can then be part of multiple categories. (In reality, this would be a many to many relationship since I am assuming that a category may obviously belong to multiple courses).
Therefore the database design will be similar to the one I created here.
If you implement this change, you would then need to make the following changes to the code you have:
// Get categories
public function categories_get($category_id)
{
if ($category_id != "") {
$this->db->where('category_id', $category_id);
}
$this->db->where('parent', 0);
$categories = $this->db->get('course_id')->result_array();
foreach ($categories as $key => $category) {
$categories[$key]['thumbnail'] = $this->get_image('category_thumbnail', $category['thumbnail']);
$categories[$key]['number_of_courses'] = $this->crud_model->get_category_wise_courses($category['id'])->num_rows();
}
return $categories;
}
Essentially, what I did here was replace the parent in the categories with category_id and category with course_id. Not sure if these are correct. You will need to review these when reading from the database as I never used CodeIgniter.

Related

get all category names of articles in index function many to many relationship

I'm trying to get only the category names, of all articles on the index() function on Laravel. So all articles and the related categories of each article. It's a many-to-many relationship.
What i did so far:
foreach($articles as $article) {
if (isset($article)) {
$article['categories'] = $article->category;
}
}
This gets me all articles with the related categories as a collection, it works, but not quite what I want. As I said, I want only the category names.
I tried $article->category->name; obviously did not work
also: $article->category->get('name');
furthermore what is interesting, If I rename $article['categories'] to $article['cat'] I get 2 collections the category with all related category objects and another exact same on just names cat.
I also tried:
...
$article['categories'] = $this->getcat($articles);
return response()->json($articles, 200);
}
public function getcat($query) {
if (isset($query) && is_countable($query)) {
foreach ($query as &$entity) {
if (!isset($entity)) { continue; }
if (isset($entity->categories)) {
$originObjects = $entity->categories;
$competition_all = [];
foreach($originObjects as $object) {
array_push($competition_all, $object->name);
}
return $competition_all;
}
}
}
The function returns what I need, but it does not return it as json, on the frontend.
From what i see.. your response is return response()->json($names, 200); , but your categories names array is saved into the $article['categories'] array. So.. if you return the response()->json($article, 200);, your categories names should be returned as json. The reason that $article->category->name; does not works is because this is array with objects because the type of your relation, but here you call only property.
The better way is to add to the query, the with function and pass the relation as first parameter, and then in second to add anonym function with the query and select only the names.
Eg.
$articles = Article::with('category', function($q) { ... })->get();

How to display articles with certain categories that match form input - Laravel

I cannot limit my search/display my results based on their categories.
I have a many to many relationship. An article has many categories and a category has many articles. A user enters an address into a form and chooses 1-3 categories. I then want to match these user specified categories with articles from my db that have these categories and only display these particular articles.
I've been looking at many SO questions like THIS ONE and can't seem to get things to work. My main error is that I get an undefined property of $categories.
Before I added the category stuff it was working and I could display a list of articles that were within a certain distance. I'm using Laravel 5.1.
Database tables:
'articles'
(id, title, etc...)
'categories'
(id, name)
'article_category'
(article_id, category_id)
Here is my code:
Article Model:
public function categories()
{
return $this->belongsToMany('App\Category')->withTimestamps();
}
public function getCategoryListAttribute()
{
return $this->categories->lists('id')->all();
}
Category model:
public function articles()
{
return $this->belongsToMany('App\Article')->withTimestamps();
}
Articles Controller
public function index(Request $request)
{
$lat = $request->get('lat');
$lng = $request->get('lng');
<--! THIS GETS THE USER DEFINED CATEGORY CHOICES -->
$categoryChoice = $request->get('categoryList');
$distance = 1;
$query = Article::getByDistance($lat, $lng, $distance);
if(empty($query)) {
return redirect()->action('HomeController#index');
}
$ids = [];
//Extracts the alticle/store id's
foreach($query as $q)
{
array_push($ids, $q->id);
}
<--! Get the listings that match the returned ids -->
$results = DB::table('articles')
->whereIn( 'id', $ids)
->orderBy('title', 'DESC')
->paginate(6);
$articles = $results;
<--! THIS IS TO GET CATEGORY IDS OF NEARBY STORES - NOT WORKING ->
$categoryMatches = [];
foreach ($articles->categories as $category){
array_push($categoryMatches, $category->pivot->category_id);
}
<--! THIS WILL IDENTIFY WHETHER THE CATEGORIES OF NEARBY STORES MATCH THE CATEGORIES CHOSEN IN THE FORM - NOT WORKING -->
$articles = $articles->whereIn($categoryMatches, $categoryChoice);
return view('articles.index', compact('categories', 'days'))->withArticles($articles);
}
Your query returns a Collection of articles. You need to iterate first on articles, because each article has many categories.
foreach ($articles as $article){
//get the article's categories ids
$article->categories->lists('id');
}

Symfony2, PHP decrease query weight when counting many objects from database

So I have 4 tables in my database. Product, Category, ProductCategory and ProductSubcategory.
These tables are in a ManyToMany relationship.
All products are saved in table: Product.
All Categories and Subcategories are saved in one table: Category.
A Product can have a Subcategory without the Category, can have both, or can have a Category without any Subcategory.
ProductCategory and ProductSubcategory is holding all the relationships.(product_id and category_id/subcategory_id)
Now in my twig I want to count how many products does the category and subcategory hold. For that I am using Twig Extensions.
For Subcategory:
Counting how many products are in a subcategory is easy and it works.
In Twig:
{% for subcategory in categories.children %}
[{{ getProductsBySubcategory(subcategory.id) }}]
{% endfor %}
The function:
public function getProductsBySubcategory($id)
{
$products = 0;
$subcategories = $this->doctrine->getRepository('MpShopBundle:ProductSubcategory')->findBy(array('subcategory' => $id));
foreach ($subcategories as $subcategory) {
$products++;
}
return $products;
}
For Category:
For category its a little bit different. Here I want to count all the products in the Category and all the products in Subcategories that this category has. First I check if the category has products and if it has I add the to the array. Then I check if this category has subcategories. If it does i get all the products from the subcategories and add them in to the same array. Now if the product is in both the category and subcategory i dont want to double count. Thats why I am storing everything in an array(to do array_unique later).
My Twig:
[{{ getProductsByCategory(categories.id)|length }}]
The function:
public function getProductsByCategory($id)
{
$category = $this->doctrine->getRepository('ApplicationSonataClassificationBundle:Category')->find($id);
$productCategories = $this->doctrine->getRepository('MpShopBundle:ProductCategory')->findBy(array('category' => $id ));
$productSubcategories = $this->doctrine->getRepository('MpShopBundle:ProductSubcategory')->findAll();
$products = array();
foreach($productCategories as $productCategory) {
array_push($products, $productCategory->getProduct()->getId());
}
foreach($productSubcategories as $productSubcategory) {
$subcategory = $productSubcategory->getSubcategory();
if($subcategory->getparent() == $category) {
array_push($products, $productSubcategory->getProduct()->getId());
}
}
$result = array_unique($products);
return $result;
}
THE PROBLEM:
Everything works in local. I am getting the correct number. However when I switch to Live I get error:500. I am thinking that this is probably because Live has thousands of products and allot of categories. So the foreach loops are breaking the server. Or maybe its because i am saving everything in array?
What should I do? Is it the server problem and I need to increase something? Or maybe there is a way to count without using arrays and decreasing the loop count? But how to check for duplicates then?
The logs show this:
Allowed memory size of 536870912 bytes exhausted (tried to allocate 77 bytes) in /var/www/kd_cms/vendor/symfony/symfony/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php on line 40
Dql is faster than foreach with large db objects
// .../ProductCatRepository.php
public function findDistinctProductsByCat($catId)
{
$query = $this->createQueryBuilder('entity')
->leftjoin('entity.category', 'cat')
->andWhere('cat.id = :cat')
->setParameter('cat', $catId)
;
return $query->getQuery()->getResult();
}
// .../ProductSubCatRepository.php
public function findDistinctProductsByCatSubCat($catId)
{
$query = $this->createQueryBuilder('entity')
->leftjoin('entity.subcategory', 'subcat')
// maybe missing a leftJoin with cat idk your mapping
// ->leftJoin('subcat.category', 'cat')
// ->andWhere('cat.parent = :cat')
->andWhere('subcat.parent = :cat')
->setParameter('cat', $catId)
;
return $query->getQuery()->getResult();
}
public function getProductsByCategory($id)
{
$categoryId = $id;
$productsCat = $this->doctrine->getRepository('MpShopBundle:ProductCat')->findDistinctProductsByCat($categoryId);
$productsSubCat = $this->doctrine->getRepository('MpShopBundle:ProductSubCat')->findDistinctProductsByCatSubCat($categoryId);
// ArrayCollection::count()
return array('count_cat' => $productsCat->count(), 'product_subcat' => $productsSubCat->count(), 'result' => array_unique(array_merge($productsSubCat, $productsCat)));
}

list items from all child menus(SQL)

So I have a table of menu, id, title, parent_id
I have attached some content fields to the most bottom level of menus(lets say id = 50), then what if I click on the top parent of that tree menu(id = 1), i want it to display the content that is attached to the child menu(doesn't matter how deep), so I guess that I need to loop through the parents, can you help me with the logic of this?
The Controller:
$data['query'] = $this->item_model->getByType($menu_id);
The Model:
function getByType($menu_id) {
if($menu_id) {
$q = $this->db->get_where('menus', array('id' => $menu_id));
$parent_id = $q->row()->parent_id;
}else{
return;
}
$this->db->select('*');
$this->db->from('items');
$this->db->where('menu_id', $menu_id);
$this->db->or_where('menu_id', $parent_id);
$query = $this->db->get();
return $query;
}
so by now I only get the items if some menu if I clicked on his parent(2 level), but how to make it infinite level of deep?, I guess that here I need to put in a loop the or_where, can you help me with that?
Hmz... Recursion!
function getChildrenAttachments($menu_id) {
// invalid menu id
if(!$menu_id) return;
// get all rows with this parent id
$query = $this->db->get_where('menus', array('parent_id' => $menu_id));
// result will hold all the attachments
$result = array();
// loop through results
foreach($query->result() as $row) {
// does this row have an attachment?
if($row->attachment!='') {
$result[] = $row->attachment;
}
// add all children attachments
$result = array_merge($result, getChildrenAttachments($row->id));
}
// return result set
return $result
}
This implementation does not take into account the current menu_id actually having an attachment. You can create a new function that takes into account that getAttachmentsForTree() which checks the current id, then then passes the id to getChildrenAttachments()...
P.S. I have not run the code, so I'm sorry for any possible issues with the code. It's just an example on how you can use recursion to your advantage in this scenario.

Display categories in Codeigniter / PHP

I am trying to display categories stored in my database:
http://dl.dropbox.com/u/16459516/Screen%20shot%202012-05-05%20at%2015.59.40.png
My idea was to get all data out of the database within my model and the reconstruct the array within my function:
function get_all_categories() {
$query = $this->db->query('SELECT * FROM categories');
//code to reconstruct my array like:
//foreach($query->result_array() as $row) {
//$categories[$row['niveau_1']] = array[]
//}
return $categories;
}
and output some array like:
[0] => Array
(
[niveau_1] => main category name
[niveau_2] => Array(
...children here and children of these childs
)
)
Does anyone know how to do this? Im stuck here and dont know how to create the array... i know how to display it in my views but getting them in the array seems hard for me.
Thanks in advance!
This is an idea:
foreach category (except level 4) check if it is present into the result array($c)
if it is not then create an array using a label the category's name
once controlle categories 2 and 3 you can insert the last one inside the generated array
$query = $this->db->query('SELECT * FROM categories');
$raw_categories = $query->result_array();
$c = array();
foreach($raw_categories AS $row)
{
if(!array_key_exists($row['niveau_2'],$c) && !empty($row['niveau_2']))
{
$c[$row['niveau_2']] = array();
}
if(!array_key_exists($row['niveau_3'],$c[$row['niveau_2']]) &&
!empty($row['niveau_3']))
{
$c[$row['niveau_2']]['niveau_3'] = array();
}
$c[$row['niveau_2']]['niveau_3'][] = $row['niveau_4'];
}
this is not tested but you can use it as an idea
I think your database is wrong. As I understand it's a "table of content" or similar (category, sub-category, sub-sub-category, etc.). "1. Visie, Beleid..." appears many-many times.
If I'd do I change the database:
table niveau_1
id_1 - primary key
title - string
table niveau_2
id_2 - primary key
id_1 - connect to niveau_1 (which niveau_1 is its 'parent')
title - string
table niveau_3
id_3 - primary key
id_2 - connect to niveau_2 (which niveau_2 is its parent)
title - string
In this case in your database will not duplicated elements and I think your desired query will be simplier:
SELECT
niveau_1.title AS title_1,
GROUP_CONCAT(niveau_2.title,"#") AS title_2,
GROUP_CONCAT(niveau_3.title,"#") AS title_3
FROM niveau_1
LEFT JOIN niveau_2 ON niveau_2.id_1=niveau_1.id_1
LEFT JOIN niveau_3 ON niveau_3.id_2=niveau_2.id_2
GROUP BY niveau_2.id_2
This query is only a half-solution, it isn't perfect for you but you can start from here.
// your model using activerecord
public function getCategories(){
$q = $this->db->get('categories');
if($q->num_rows > 0){
return $q->result();
}else{
return false;
}
}
//your controller
$this->load->model('category_model');
$data['results'] = $this->category_model->getCategories();
foreach($data['results'] as $c){
//iterate through categories, match them to points.
//match category_id in results with category_id in the other table results
//setup a multi dimensional array to build up what you need.
}

Categories