From 42abf4c57664d2596872d437f70b95193f9a5d18 Mon Sep 17 00:00:00 2001 From: plegall Date: Sun, 2 Apr 2006 22:26:19 +0000 Subject: improvement: tags replace keywords. Better data model, less limitations. Each image can be associated to as many tag as needed. Tags can contain non ASCII characters. Oriented navigation with tags by association. git-svn-id: http://piwigo.org/svn/trunk@1119 68402e56-0260-453c-a942-63ccdbb3a9ee --- include/category_default.inc.php | 2 +- include/common.inc.php | 1 + include/config_default.inc.php | 13 ++- include/constants.php | 2 + include/functions.inc.php | 33 +++++- include/functions_html.inc.php | 53 +++++++++- include/functions_search.inc.php | 68 ++++++++++-- include/functions_tag.inc.php | 218 +++++++++++++++++++++++++++++++++++++++ include/functions_url.inc.php | 34 +++--- include/functions_user.inc.php | 1 - include/section_init.inc.php | 117 ++++++++++++++++++--- 11 files changed, 496 insertions(+), 46 deletions(-) create mode 100644 include/functions_tag.inc.php (limited to 'include') diff --git a/include/category_default.inc.php b/include/category_default.inc.php index 31d40ed90..6c909647a 100644 --- a/include/category_default.inc.php +++ b/include/category_default.inc.php @@ -79,7 +79,7 @@ foreach ($pictures as $row) { $thumbnail_title .= ' : '.$row['filesize'].' KB'; } - + // link on picture.php page $url = duplicate_picture_url( array( diff --git a/include/common.inc.php b/include/common.inc.php index 42697353e..f66d1ee7f 100644 --- a/include/common.inc.php +++ b/include/common.inc.php @@ -148,6 +148,7 @@ SELECT id if (count(array_diff($existing, $applied)) > 0) { ob_start();// buffer output so that cookies work + echo '

' .'Some database upgrades are missing, ' diff --git a/include/config_default.inc.php b/include/config_default.inc.php index 3675f2ed6..57a4520c3 100644 --- a/include/config_default.inc.php +++ b/include/config_default.inc.php @@ -438,6 +438,18 @@ $conf['picture_url_style'] = 'id'; // are active. $conf['php_extension_in_urls'] = true; +// +-----------------------------------------------------------------------+ +// | tags | +// +-----------------------------------------------------------------------+ + +// full_tag_cloud_items_number: number of tags to show in the full tag +// cloud. Only the most represented tags will be shown +$conf['full_tag_cloud_items_number'] = 200; + +// tags_levels: number of levels to use for display. Each level is bind to a +// CSS class tagLevelX. +$conf['tags_levels'] = 10; + // +-----------------------------------------------------------------------+ // | Notification by mail | // +-----------------------------------------------------------------------+ @@ -455,5 +467,4 @@ $conf['nbm_list_all_enabled_users_to_send'] = false; // Max mails sended on one pass $conf['nbm_max_mails_send'] = 35; - ?> diff --git a/include/constants.php b/include/constants.php index 089e3b65d..02b352cc3 100644 --- a/include/constants.php +++ b/include/constants.php @@ -70,4 +70,6 @@ define('UPGRADE_TABLE', $prefixeTable.'upgrade'); define('SEARCH_TABLE', $prefixeTable.'search'); define('USER_MAIL_NOTIFICATION_TABLE', $prefixeTable.'user_mail_notification'); define('CATEGORIES_LINK_TABLE', $prefixeTable.'categories_link'); +define('TAGS_TABLE', $prefixeTable.'tags'); +define('IMAGE_TAG_TABLE', $prefixeTable.'image_tag'); ?> diff --git a/include/functions.inc.php b/include/functions.inc.php index 981da55c4..d8b86743f 100644 --- a/include/functions.inc.php +++ b/include/functions.inc.php @@ -31,6 +31,7 @@ include_once( PHPWG_ROOT_PATH .'include/functions_category.inc.php' ); include_once( PHPWG_ROOT_PATH .'include/functions_xml.inc.php' ); include_once( PHPWG_ROOT_PATH .'include/functions_group.inc.php' ); include_once( PHPWG_ROOT_PATH .'include/functions_html.inc.php' ); +include_once( PHPWG_ROOT_PATH .'include/functions_tag.inc.php' ); include_once( PHPWG_ROOT_PATH .'include/functions_url.inc.php' ); //----------------------------------------------------------- generic functions @@ -267,6 +268,35 @@ function get_picture_size( $original_width, $original_height, $picture_size[1] = $height; return $picture_size; } + +/** + * simplify a string to insert it into an URL + * + * based on str2url function from Dotclear + * + * @param string + * @return string + */ +function str2url($str) +{ + $str = strtr( + $str, + 'ÀÁÂÃÄÅàáâãäåÇçÒÓÔÕÖØòóôõöøÈÉÊËèéêëÌÍÎÏìíîïÙÚÛÜùúûü¾ÝÿýÑñ', + 'AAAAAAaaaaaaCcOOOOOOooooooEEEEeeeeIIIIiiiiUUUUuuuuYYyyNn' + ); + + $str = str_replace('Æ', 'AE', $str); + $str = str_replace('æ', 'ae', $str); + $str = str_replace('¼', 'OE', $str); + $str = str_replace('½', 'oe', $str); + + $str = preg_replace('/[^a-z0-9_\s\'\:\/\[\]-]/','',strtolower($str)); + $str = preg_replace('/[\s\'\:\/\[\]-]+/',' ',trim($str)); + $res = str_replace(' ','_',$str); + + return $res; +} + //-------------------------------------------- PhpWebGallery specific functions /** @@ -829,5 +859,4 @@ function get_available_upgrade_ids() return $available_upgrade_ids; } - -?> \ No newline at end of file +?> diff --git a/include/functions_html.inc.php b/include/functions_html.inc.php index 7e7df7c41..13c3dc206 100644 --- a/include/functions_html.inc.php +++ b/include/functions_html.inc.php @@ -5,7 +5,6 @@ // | Copyright (C) 2003-2005 PhpWebGallery Team - http://phpwebgallery.net | // +-----------------------------------------------------------------------+ // | branch : BSF (Best So Far) -// | file : $Id$ // | last update : $Date$ // | last modifier : $Author$ // | revision : $Revision$ @@ -494,6 +493,56 @@ function get_cat_display_name_from_id($cat_id, return get_cat_display_name($cat_info['name'], $url, $replace_space); } +/** + * Returns an HTML list of tags. It can be a multi select field or a list of + * checkboxes. + * + * @param string HTML field name + * @param array selected tag ids + * @return array + */ +function get_html_tag_selection( + $tags, + $fieldname, + $selecteds = array(), + $forbidden_categories = null + ) +{ + global $conf; + + $output = '

'; + + return $output; +} + +function name_compare($a, $b) +{ + return strcmp($a['name'], $b['name']); +} + /** * exits the current script (either exit or redirect) */ @@ -519,4 +568,4 @@ function access_denied() redirect($login_url); } } -?> \ No newline at end of file +?> diff --git a/include/functions_search.inc.php b/include/functions_search.inc.php index 2ca87969e..7e55160c8 100644 --- a/include/functions_search.inc.php +++ b/include/functions_search.inc.php @@ -84,7 +84,7 @@ function get_sql_search_clause($search_id) // construction $clauses = array(); - foreach (array('file','name','comment','keywords','author') as $textfield) + foreach (array('file','name','comment','author') as $textfield) { if (isset($search['fields'][$textfield])) { @@ -109,7 +109,7 @@ function get_sql_search_clause($search_id) if (isset($search['fields']['allwords'])) { - $fields = array('file', 'name', 'comment', 'keywords', 'author'); + $fields = array('file', 'name', 'comment', 'author'); // in the OR mode, request bust be : // ((field1 LIKE '%word1%' OR field2 LIKE '%word1%') // OR (field1 LIKE '%word2%' OR field2 LIKE '%word2%')) @@ -208,12 +208,68 @@ function get_sql_search_clause($search_id) $search_clause = $where_separator; - if (isset($forbidden)) + return $search_clause; +} + +/** + * returns the list of items corresponding to the search id + * + * @param int search id + * @return array + */ +function get_search_items($search_id) +{ + $items = array(); + + $search_clause = get_sql_search_clause($search_id); + + if (!empty($search_clause)) { - $search_clause.= "\n AND ".$forbidden; + $query = ' +SELECT DISTINCT(id) + FROM '.IMAGES_TABLE.' + INNER JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON id = ic.image_id + WHERE '.$search_clause.' +;'; + $items = array_from_query($query, 'id'); } - return $search_clause; -} + $search = get_search_array($search_id); + if (isset($search['fields']['tags'])) + { + $tag_items = get_image_ids_for_tags( + $search['fields']['tags']['words'], + $search['fields']['tags']['mode'] + ); + + switch ($search['mode']) + { + case 'AND': + { + if (empty($search_clause)) + { + $items = $tag_items; + } + else + { + $items = array_intersect($items, $tag_items); + } + break; + } + case 'OR': + { + $items = array_unique( + array_merge( + $items, + $tag_items + ) + ); + break; + } + } + } + + return $items; +} ?> \ No newline at end of file diff --git a/include/functions_tag.inc.php b/include/functions_tag.inc.php new file mode 100644 index 000000000..5dd4884f4 --- /dev/null +++ b/include/functions_tag.inc.php @@ -0,0 +1,218 @@ += 1; $i--) + { + if ($tags[$k]['counter'] > $threshold_of_level[$i]) + { + $tags[$k]['level'] = $i + 1; + break; + } + } + } + + return $tags; +} + +/** + * return the list of image ids corresponding to given tags. AND & OR mode + * supported. + * + * @param array tag ids + * @param string mode + * @return array + */ +function get_image_ids_for_tags($tag_ids, $mode = 'AND') +{ + switch ($mode) + { + case 'AND': + { + // strategy is to list images associated to each tag + $tag_images = array(); + + foreach ($tag_ids as $tag_id) + { + $query = ' +SELECT image_id + FROM '.IMAGE_TAG_TABLE.' + WHERE tag_id = '.$tag_id.' +;'; + $tag_images[$tag_id] = array_from_query($query, 'image_id'); + } + + // then we calculate the intersection, the images that are associated to + // every tags + $items = array_shift($tag_images); + foreach ($tag_images as $images) + { + $items = array_intersect($items, $images); + } + + return array_unique($items); + break; + } + case 'OR': + { + $query = ' +SELECT DISTINCT image_id + FROM '.IMAGE_TAG_TABLE.' + WHERE tag_id IN ('.implode(',', $tag_ids).') +;'; + return array_from_query($query, 'image_id'); + break; + } + default: + { + die('get_image_ids_for_tags: unknown mode, only AND & OR are supported'); + } + } +} +?> \ No newline at end of file diff --git a/include/functions_url.inc.php b/include/functions_url.inc.php index 0e0583872..a50f789c8 100644 --- a/include/functions_url.inc.php +++ b/include/functions_url.inc.php @@ -258,23 +258,18 @@ function make_section_in_URL($params) { $section_string = ''; - if (!isset($params['section'])) + $section_of = array( + 'category' => 'categories', + 'tags' => 'tags', + 'list' => 'list', + 'search' => 'search', + ); + + foreach ($section_of as $param => $section) { - if (isset($params['category'])) - { - $params['section'] = 'categories'; - } - else if (isset($params['tags'])) - { - $params['section'] = 'tags'; - } - else if (isset($params['list'])) - { - $params['section'] = 'list'; - } - else if (isset($params['search'])) + if (isset($params[$param])) { - $params['section'] = 'search'; + $params['section'] = $section; } } @@ -289,7 +284,7 @@ function make_section_in_URL($params) { if (!isset($params['category'])) { - //$section_string.= '/categories'; + $section_string.= '/categories'; } else { @@ -309,7 +304,12 @@ function make_section_in_URL($params) foreach ($params['tags'] as $tag) { - $section_string.= '/'.$tag; + $section_string.= '/'.$tag['id']; + + if (isset($tag['url_name'])) + { + $section_string.= '-'.$tag['url_name']; + } } break; diff --git a/include/functions_user.inc.php b/include/functions_user.inc.php index cfa4d53aa..c5efa98ad 100644 --- a/include/functions_user.inc.php +++ b/include/functions_user.inc.php @@ -633,5 +633,4 @@ function is_adviser() return ($user['adviser'] == 'true'); } - ?> \ No newline at end of file diff --git a/include/section_init.inc.php b/include/section_init.inc.php index cc7c074c2..e2ed07b42 100644 --- a/include/section_init.inc.php +++ b/include/section_init.inc.php @@ -145,15 +145,11 @@ else if (0 === strpos($tokens[$next_token], 'tag')) $page['tags'] = array(); $next_token++; + $i = $next_token; - for ($i = $next_token; ; $i++) + while (isset($tokens[$i])) { - if (!isset($tokens[$i])) - { - break; - } - - preg_match('/^(\d+)/', $tokens[$i], $matches); + preg_match('/^(\d+)(?:-(.*))?/', $tokens[$i], $matches); if (!isset($matches[1])) { if (0 == count($page['tags'])) @@ -165,7 +161,16 @@ else if (0 === strpos($tokens[$next_token], 'tag')) break; } } - array_push($page['tags'], $matches[1]); + + array_push( + $page['tags'], + array( + 'id' => $matches[1], + 'url_name' => isset($matches[2]) ? $matches[2] : '', + ) + ); + + $i++; } $next_token = $i; @@ -225,13 +230,10 @@ else if ('list' == $tokens[$next_token]) $next_token++; } -for ($i = $next_token; ; $i++) -{ - if (!isset($tokens[$i])) - { - break; - } +$i = $next_token; +while (isset($tokens[$i])) +{ if (preg_match('/^start-(\d+)/', $tokens[$i], $matches)) { $page['start'] = $matches[1]; @@ -240,9 +242,12 @@ for ($i = $next_token; ; $i++) if (preg_match('/^posted|created/', $tokens[$i] )) { $chronology_tokens = explode('-', $tokens[$i] ); + $page['chronology_field'] = $chronology_tokens[0]; + array_shift($chronology_tokens); $page['chronology_style'] = $chronology_tokens[0]; + array_shift($chronology_tokens); if ( count($chronology_tokens)>0 ) { @@ -255,6 +260,8 @@ for ($i = $next_token; ; $i++) $page['chronology_date'] = $chronology_tokens; } } + + $i++; } @@ -338,19 +345,97 @@ else $forbidden = ' 1 = 1'; } // +-----------------------------------------------------------------------+ +// | tags section | +// +-----------------------------------------------------------------------+ + if ($page['section'] == 'tags') + { + $page['tag_ids'] = array(); + foreach ($page['tags'] as $tag) + { + array_push($page['tag_ids'], $tag['id']); + } + + $items = get_image_ids_for_tags($page['tag_ids']); + + // permissions depends on category, so to only keep images that are + // reachable to the connected user, we need to check category + // associations + if (!empty($user['forbidden_categories'])) + { + $query = ' +SELECT image_id + FROM '.IMAGE_CATEGORY_TABLE.' + WHERE image_id IN ('.implode(',', $items).') + AND '.$forbidden.' +;'; + $items = array_unique( + array_from_query($query, 'image_id') + ); + } + + // tag names + $query = ' +SELECT name, url_name, id + FROM '.TAGS_TABLE.' + WHERE id IN ('.implode(',', $page['tag_ids']).') +;'; + $result = pwg_query($query); + $tag_infos = array(); + + while ($row = mysql_fetch_array($result)) + { + $tag_infos[ $row['id'] ]['name'] = $row['name']; + $tag_infos[ $row['id'] ]['url_name'] = $row['url_name']; + } + + $title = count($page['tags']) > 1 ? l10n('Tags') : l10n('Tag'); + $title.= ' '; + + $tag_num = 1; + foreach ($page['tag_ids'] as $tag_id) + { + $title.= + ($tag_num++ > 1 ? ' + ' : '') + .'' + .$tag_infos[$tag_id]['name'] + .''; + } + + $page = array_merge( + $page, + array( + 'title' => $title, + 'items' => array_values($items), + 'thumbnails_include' => 'include/category_default.inc.php', + ) + ); + } +// +-----------------------------------------------------------------------+ // | search section | // +-----------------------------------------------------------------------+ if ($page['section'] == 'search') { include_once( PHPWG_ROOT_PATH .'include/functions_search.inc.php' ); + $query = ' SELECT DISTINCT(id) FROM '.IMAGES_TABLE.' INNER JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON id = ic.image_id - WHERE '.get_sql_search_clause($page['search']).' + WHERE id IN ('.implode(',', get_search_items($page['search'])).') AND '.$forbidden.' '.$conf['order_by'].' -;'; +;'; $page = array_merge( $page, -- cgit v1.2.3