diff --git a/application/config/migration.php b/application/config/migration.php index 397a47a..185232a 100644 --- a/application/config/migration.php +++ b/application/config/migration.php @@ -2,5 +2,5 @@ $config['migration_enabled'] = TRUE; $config['migration_type'] = 'sequential'; -$config['migration_version'] = 2; +$config['migration_version'] = 4; $config['migration_path'] = APPPATH . 'migrations/'; diff --git a/application/config/routes.php b/application/config/routes.php index a5ba9d0..b9f322e 100644 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -121,6 +121,8 @@ $route['group/(:num)'] = "catalog/group/index/$1"; $route['group/get_results'] = "catalog/group/get_results"; +$route['keywords/(:num)'] = "catalog/keywords/index/$1"; +$route['keywords/get_results'] = "catalog/keywords/get_results"; $route['sections/readers/(:num)'] = 'catalog/sections/readers/$1'; @@ -166,4 +168,4 @@ $route['(:any)'] = "catalog/page/index/$1"; /* End of file routes.php */ -/* Location: ./application/config/routes.php */ \ No newline at end of file +/* Location: ./application/config/routes.php */ diff --git a/application/controllers/catalog/Keywords.php b/application/controllers/catalog/Keywords.php new file mode 100644 index 0000000..a454ed1 --- /dev/null +++ b/application/controllers/catalog/Keywords.php @@ -0,0 +1,108 @@ +load->helper('general_functions_helper'); + } + + public function index($keywords_id) + { + if (empty($keywords_id)) { + show_404(); + } + $this->load->model('keyword_model'); + $this->data['keywords'] = $this->keyword_model->get($keywords_id); + $this->data['search_category'] = 'keywords'; + $this->data['primary_key'] = $keywords_id; + $this->data['search_order'] = $this->input->get('search_order'); + $params['keywords_id'] = $keywords_id; + $params['offset'] = 0; + $params['limit'] = 1000000; + $params['search_order'] = $this->input->get('search_order'); + $matches = $this->_get_projects_by_keywords_id($params); + $this->data['matches'] = count($matches); + $this->_render('catalog/keywords'); + return; + } + + function get_results() + { + + // collect - search_category, sub_category, page_number, sort_order + // and (sometimes) information about contents of project_type + $input = $this->input->get(null, true); + + $params['keywords_id'] = $input['primary_key']; + $params['search_order'] = $input['search_order']; + + if (empty($params['keywords_id'])) { + show_error('A primary_key (keywords ID) must be supplied', 400); + } + + //format offset + $params['offset'] = ($input['search_page'] - 1) * CATALOG_RESULT_COUNT; + + // format limit + $params['limit'] = CATALOG_RESULT_COUNT; + + + + // format project_type_description. Note that values appearing here are not + // necessarily a match to values that appear in the projects.project_type column + // in the database - so we'll need to massage them later (in Project model->get_projects_by_keywords_id()). + if (array_key_exists('project_type', $input)) + { + $params['project_type_description'] = $input['project_type']; + } else + { + $params['project_type_description'] = 'either'; + } + + // go get results + $results = $this->_get_projects_by_keywords_id($params); + + // get full set by calling same function with different parameters + $params['offset'] = 0; + $params['limit'] = 1000000; + + $full_set = $this->_get_projects_by_keywords_id($params); + + // go format results + $retval['results'] = $this->_format_results($results, 'title'); + + //pagination + $page_count = (count($full_set) > CATALOG_RESULT_COUNT) ? ceil(count($full_set) / CATALOG_RESULT_COUNT) : 0; + $retval['pagination'] = (empty($page_count)) ? '' : $this->_format_pagination($input['search_page'], $page_count); + + $retval['status'] = 'SUCCESS'; + + //return - results, pagination + if ($this->input->is_ajax_request()) + { + header('Content-Type: application/json;charset=utf-8'); + echo json_encode($retval); + return; + } + } + + function _get_projects_by_keywords_id($params) + { + $this->load->model('project_model'); + $projects = $this->project_model->get_projects_by_keywords_id($params); + + foreach ($projects as $key => $project) + { + $projects[$key]['author_list'] = $this->_author_list($project['id']); + } + + return $projects; + } +} diff --git a/application/controllers/catalog/Page.php b/application/controllers/catalog/Page.php index 1fac57e..e74ba54 100644 --- a/application/controllers/catalog/Page.php +++ b/application/controllers/catalog/Page.php @@ -61,6 +61,20 @@ public function index($slug) $this->data['volunteers']->bc = $this->user_model->get($this->data['project']->person_bc_id); $this->data['volunteers']->mc = $this->user_model->get($this->data['project']->person_mc_id); $this->data['volunteers']->pl = $this->user_model->get($this->data['project']->person_pl_id); + + + // **** KEYWORDS ****// + $MAX_KEYWORDS_TO_DISPLAY_INITIALLY = 10; + $this->data['keywords_array'] = $this->project_model->get_keywords_and_statistics_by_project($this->data['project']->id); + $this->data['project']->has_long_keywords_list = $this->_has_long_keywords_list($this->data['project']->id, $MAX_KEYWORDS_TO_DISPLAY_INITIALLY); + $this->data['project']->formatted_keywords_string_primary = $this->_formatted_keywords_string($this->data['project']->id, + $this->data['project']->has_long_keywords_list, + $MAX_KEYWORDS_TO_DISPLAY_INITIALLY, + "primary_list"); + $this->data['project']->formatted_keywords_string_secondary = $this->_formatted_keywords_string($this->data['project']->id, + $this->data['project']->has_long_keywords_list, + $MAX_KEYWORDS_TO_DISPLAY_INITIALLY, + "secondary_list"); // **** MISC ****// $this->data['project']->project_type = (trim($this->data['project']->project_type) == 'solo') ? 'solo' : 'group'; //keep on top - other values dependent on solo/group @@ -170,7 +184,123 @@ function _genre_list($project_id) return implode(', ', $genres); } - + + function _formatted_keywords_string($project_id, $project_has_long_keywords_list, + $max_keywords_to_display_initially, $list_variant) + { + /** The catalog page will contain one, or possibly two lists of keywords. + It will always contain a primary list. + If the project has more keywords than can conveniently be fitted in the primary list: + (a) We truncate this primary list + (b) We also output to the page a secondary list, iniitially hidden, that includes all the keywords for the project + (c) We add to the primary list a "Show full list" link which, if clicked, hides the primary list + and shows the secondary list + */ + + $return_value = ""; + + switch ($list_variant) { + case "primary_list": + if ($project_has_long_keywords_list) + { + $return_value = $this->_formatted_truncated_keywords_string($project_id, $max_keywords_to_display_initially); + } + else + { + $return_value = $this->_formatted_full_keywords_string($project_id); + } + break; + case "secondary_list"; + if ($project_has_long_keywords_list) + { + $return_value = $this->_formatted_full_keywords_string($project_id); + } + break; + default: + $return_value = ""; + } + return $return_value; + } + + + function _formatted_full_keywords_string($project_id) + { + $return_value = ''; + $keywords_array = $this->data['keywords_array']; + if (!empty($keywords_array)) + { + foreach ($keywords_array as $key => $row) + { + // Add hyperlink only if at least two projects use this keyword + if ((int)$row['keyword_count'] > 1) + { + $return_value .= ''; + } + $return_value .= $row['value']; + if ((int)$row['keyword_count'] > 1) + { + $return_value .= ''; + } + $return_value .= ' (' . $row['keyword_count'] . '), '; + } + } + else + { + return $return_value; + } + // trim off final comma and trailing space + $return_value = substr($return_value, 0, -2); + return $return_value; + } + + function _formatted_truncated_keywords_string($project_id, $max_keywords_to_display_initially) + { + $return_value = ''; + $keywords_array = $this->data['keywords_array']; + if (!empty($keywords_array)) + { + $i = 0; + foreach ($keywords_array as $key => $row) + { + + // Add hyperlink only if at least two projects use this keyword + if ((int)$row['keyword_count'] > 1) + { + $return_value .= ''; + } + $return_value .= $row['value']; + if ((int)$row['keyword_count'] > 1) + { + $return_value .= ''; + } + $return_value .= ' (' . $row['keyword_count'] . '), '; + $i++; + if ($i == $max_keywords_to_display_initially) + { + break; + } + } + } + else + { + return $return_value; + } + // trim off final comma and trailing space + $return_value = substr($return_value, 0, -2); + + // Add "Show full list" link + $return_value .= " ... [Show full list]"; + + return $return_value; + } + + function _has_long_keywords_list($project_id, $max_keywords_to_display_initially) + { + $keywords_number = $this->project_model->get_keywords_count_by_project($project_id)->keywords_number; + return $keywords_number > $max_keywords_to_display_initially; + } + + function _language($language_id) { $this->load->model('language_model'); diff --git a/application/controllers/cron/Keywords_stats.php b/application/controllers/cron/Keywords_stats.php new file mode 100644 index 0000000..e25a778 --- /dev/null +++ b/application/controllers/cron/Keywords_stats.php @@ -0,0 +1,32 @@ +db->query($sql); + + $sql = ' + UPDATE keywords + JOIN + (SELECT pk.keyword_id as keyword_id, COUNT(pk.project_id) AS count + FROM project_keywords pk + GROUP BY 1 + ) as sub + ON keywords.id = sub.keyword_id + SET keywords.count = sub.count '; + $this->db->query($sql); + + } +} + + +/* End of file Keyword_stats.php */ +/* Location: ./application/controllers/cron/Keyword_stats.php */ + diff --git a/application/libraries/Catalog_item.php b/application/libraries/Catalog_item.php index 7139a33..0e8b853 100644 --- a/application/libraries/Catalog_item.php +++ b/application/libraries/Catalog_item.php @@ -211,6 +211,9 @@ private function _process_keywords($project_id, $keywords_tag) $this->ci->project_keyword_model->insert(array('project_id'=>$project_id)); } } + // For keywords used in this project, update 'instances' field of 'keywords' table now, + // so correct keyword instances stats can be shown on catalog page even before keywords cron stats job runs. + $this->ci->project_keyword_model->set_keywords_statistics_by_project($project_id); } diff --git a/application/migrations/003_add_keywords_count_field.php b/application/migrations/003_add_keywords_count_field.php new file mode 100644 index 0000000..c631548 --- /dev/null +++ b/application/migrations/003_add_keywords_count_field.php @@ -0,0 +1,22 @@ + array('type' => 'INT', + 'default' => '0', + 'after' => 'value') + ); + $this->dbforge->add_column('keywords', $fields); + } + + public function down() + { + $this->dbforge->drop_column('keywords', 'count'); + } +} + diff --git a/application/migrations/004_drop_redundant_keywords_from_projects.php b/application/migrations/004_drop_redundant_keywords_from_projects.php new file mode 100644 index 0000000..b3ab102 --- /dev/null +++ b/application/migrations/004_drop_redundant_keywords_from_projects.php @@ -0,0 +1,52 @@ +db->query($sql); + } + + public function down() + { + ; + } +} diff --git a/application/models/Project_keyword_model.php b/application/models/Project_keyword_model.php index 742afc7..a542e2f 100644 --- a/application/models/Project_keyword_model.php +++ b/application/models/Project_keyword_model.php @@ -2,7 +2,26 @@ class Project_keyword_model extends MY_Model { - + public function set_keywords_statistics_by_project($project_id) + { + $sql = ' + UPDATE keywords k + JOIN + (SELECT pk.keyword_id as keyword_id, COUNT(pk.project_id) AS count + FROM project_keywords pk + GROUP BY 1 + ) as sub + ON k.id = sub.keyword_id + SET k.count = sub.count + WHERE k.id IN + (SELECT keyword_id + FROM project_keywords + WHERE project_id = ?)'; + + + $query = $this->db->query($sql, array($project_id)); + } + } diff --git a/application/models/Project_model.php b/application/models/Project_model.php index 20c2e19..99ea6d5 100644 --- a/application/models/Project_model.php +++ b/application/models/Project_model.php @@ -363,6 +363,85 @@ function get_frozen_projects() return $query->result(); } + + + public function get_projects_by_keywords_id($params) + { + // If calls to this function do not pass a project_type_description parameter, + // provide a sensible default + + $keyword_id = $params['keywords_id']; + $offset = 0 + $params['offset']; + $limit = 0 + $params['limit']; + + $sql = 'select p.id, p.title_prefix, p.title, COALESCE(p.date_catalog, "2001-01-01" ) as safe_release_date, + p.url_librivox, p.status, p.coverart_thumbnail, p.zip_url, p.zip_size, l.two_letter_code, l.language, p.url_forum, p.project_type + FROM keywords k + JOIN project_keywords pk ON (k.id = pk.keyword_id) + JOIN projects p on (pk.project_id = p.id) + JOIN languages l ON (l.id = p.language_id) '; + $sql .= 'WHERE k.id = ? '; + + + // It is likely that either $params['project_type_description'] will not exist as a key at all, + // or that if it does exist, it will contain the values of 'either','solo' or 'group', matching values passed + // from Javascript embedded in the search page footer. Of these values, only 'solo' matches a value actually to be + // found in the projects.project_type field. + + if (array_key_exists('project_type_description', $params)) + { + if ($params['project_type_description'] == 'group') + { + $sql .= 'AND p.project_type != "solo" '; + } + elseif ($params['project_type_description'] == 'solo') + { + $sql .= 'AND p.project_type = "solo" '; + } + } + + if ($params['search_order'] == 'catalog_date') + { + $sql .= ' ORDER BY safe_release_date DESC '; + } else + { + $sql .= ' ORDER BY p.title ASC '; + } + $sql .= ' LIMIT ? , ? '; + $query = $this->db->query($sql, array($keyword_id, $offset, $limit)); + + return $query->result_array(); + + } + + + public function get_keywords_and_statistics_by_project($project_id) + { + $sql = ' + SELECT keywords.id, keywords.value, keywords.count as keyword_count + FROM keywords + JOIN project_keywords + ON keywords.id = project_keywords.keyword_id + WHERE project_keywords.project_id = ? + ORDER BY keywords.count DESC' ; + + $query = $this->db->query($sql, array($project_id)); + if ($query->num_rows() > 0) return $query->result_array(); + return ''; + } + + public function get_keywords_count_by_project($project_id) + { + $sql = ' + SELECT COUNT(keywords.id) as keywords_number + FROM keywords + JOIN project_keywords + ON keywords.id = project_keywords.keyword_id + WHERE project_keywords.project_id = ?'; + + $query = $this->db->query($sql, array($project_id)); + return $query->row(); + } diff --git a/application/views/catalog/keywords.php b/application/views/catalog/keywords.php new file mode 100644 index 0000000..3eb1a9f --- /dev/null +++ b/application/views/catalog/keywords.php @@ -0,0 +1,39 @@ += $header; ?> + +