From 4f3880af89641248fa22b2e1c73226dc568b9340 Mon Sep 17 00:00:00 2001 From: plegall Date: Mon, 9 Nov 2020 13:00:01 +0100 Subject: [PATCH] fixes #626 related albums (menu, combine, url) * ability to combine albums in URL index.php?/category/1/2/3 will consider 1 as $page['category'] and {2,3} as $page['combined_categories'] * new menu block "related albums" which displays only the hierarchy of albums related to the current set of photos, excluding current album(s). In the hierarchy, only the related albums have a link, not ancestors --- include/functions_category.inc.php | 188 ++++++++++++++++++ include/functions_html.inc.php | 50 +++++ include/functions_url.inc.php | 44 +++- include/menubar.inc.php | 28 +++ include/section_init.inc.php | 18 +- language/en_UK/common.lang.php | 1 + language/fr_FR/common.lang.php | 3 +- .../template/menubar_related_categories.tpl | 28 +++ themes/default/theme.css | 34 +++- 9 files changed, 388 insertions(+), 6 deletions(-) create mode 100644 themes/default/template/menubar_related_categories.tpl diff --git a/include/functions_category.inc.php b/include/functions_category.inc.php index 7bf8fb0f2..d0124aac9 100644 --- a/include/functions_category.inc.php +++ b/include/functions_category.inc.php @@ -602,4 +602,192 @@ function remove_computed_category(&$cats, $cat) unset($cats[$cat['cat_id']]); } +/** + * Return the list of image ids corresponding to given categories. + * AND & OR mode supported. + * + * @param int[] $cat_ids + * @param string mode + * @param string $extra_images_where_sql - optionally apply a sql where filter to retrieved images + * @param string $order_by - optionally overwrite default photo order + * @param bool $user_permissions + * @return array + */ +function get_image_ids_for_categories($cat_ids, $mode='AND', $extra_images_where_sql='', $order_by='', $use_permissions=true) +{ + global $conf; + + if (empty($cat_ids)) + { + return array(); + } + + $query = ' +SELECT id + FROM '.IMAGES_TABLE.' i + INNER JOIN '.IMAGE_CATEGORY_TABLE.' ic ON id=ic.image_id + WHERE category_id IN ('.implode(',', $cat_ids).')'; + + if ($use_permissions) + { + $query.= get_sql_condition_FandF( + array( + 'forbidden_categories' => 'category_id', + 'visible_categories' => 'category_id', + 'visible_images' => 'id' + ), + "\n AND" + ); + } + + $query.= (empty($extra_images_where_sql) ? '' : " \nAND (".$extra_images_where_sql.')').' + GROUP BY id'; + + if ($mode=='AND' and count($cat_ids)>1) + { + $query .= ' + HAVING COUNT(DISTINCT category_id)='.count($cat_ids); + } + $query .= "\n".(empty($order_by) ? $conf['order_by'] : $order_by); + + return query2array($query, null, 'id'); +} + +/** + * Return a list of categories corresponding to given items. + * + * @param int[] $items + * @param int $max + * @param int[] $excluded_cat_ids + * @return array [id, name, counter, url_name] + */ +function get_common_categories($items, $max=null, $excluded_cat_ids=array()) +{ + if (empty($items)) + { + return array(); + } + + $query = ' +SELECT + c.id, + c.uppercats, + count(*) AS counter + FROM '.IMAGE_CATEGORY_TABLE.' + INNER JOIN '.CATEGORIES_TABLE.' c ON category_id = id + WHERE image_id IN ('.implode(',', $items).')'; + + if (!empty($excluded_cat_ids)) + { + $query.=' + AND category_id NOT IN ('.implode(',', $excluded_cat_ids).')'; + } + + $query .=' + GROUP BY c.id + ORDER BY '; + if (isset($max)) + { + $query .= 'counter DESC + LIMIT '.$max; + } + else + { + $query .= 'NULL'; + } + + $result = pwg_query($query); + $cats = array(); + while ($row = pwg_db_fetch_assoc($result)) + { + $cats[ $row['id'] ] = $row; + } + + return $cats; +} + +function get_related_categories_menu($items, $excluded_cat_ids=array()) +{ + global $page; + + $common_cats = get_common_categories($items, null, $excluded_cat_ids); + // echo '
'; print_r($common_cats); echo '
'; + + if (count($common_cats) == 0) + { + return array(); + } + + $cat_ids = array(); + // now we add the upper categories and useful values such as depth level and url + foreach ($common_cats as $cat) + { + foreach (explode(',', $cat['uppercats']) as $uppercat) + { + @$cat_ids[$uppercat]++; + } + } + + $query = ' +SELECT + id, + name, + permalink, + id_uppercat, + uppercats, + global_rank + FROM '.CATEGORIES_TABLE.' + WHERE id IN ('.implode(',', array_keys($cat_ids)).') +;'; + $cats = query2array($query); + usort($cats, 'global_rank_compare'); + + $index_of_cat = array(); + + foreach ($cats as $idx => $cat) + { + $index_of_cat[ $cat['id'] ] = $idx; + $cats[$idx]['LEVEL'] = substr_count($cat['global_rank'], '.') + 1; + + // if the category is directly linked to the items, we add an URL + counter + if (isset($common_cats[ $cat['id'] ])) + { + $cats[$idx]['count_images'] = $common_cats[ $cat['id'] ]['counter']; + + $url_params = array(); + if (isset($page['category'])) + { + $url_params['category'] = $page['category']; + + $url_params['combined_categories'] = array($cat); + if (isset($page['combined_categories'])) + { + $url_params['combined_categories'] = array_merge($page['combined_categories'], array($cat)); + } + } + else + { + $url_params['category'] = $cat; + } + + $cats[$idx]['url'] = make_index_url($url_params); + } + + // let's find how many sub-categories we have for each category. 3 options: + // 1. direct sub-albums + // 2. total indirect sub-albums + // 3. number of sub-albums containing photos + // + // Option 3 seems more appropriate here. + if (!empty($cat['id_uppercat']) and @$cats[$idx]['count_images'] > 0) + { + foreach (array_slice(explode(',', $cat['uppercats']), 0, -1) as $uppercat_id) + { + @$cats[ $index_of_cat[ $uppercat_id ] ]['count_categories']++; + } + } + } + + return $cats; +} ?> diff --git a/include/functions_html.inc.php b/include/functions_html.inc.php index 417b5f19f..01c54e79d 100644 --- a/include/functions_html.inc.php +++ b/include/functions_html.inc.php @@ -441,6 +441,55 @@ function get_tags_content_title() return $title; } +/** + * Returns the breadcrumb to be displayed above thumbnails on combined categories page. + * + * @return string + */ +function get_combined_categories_content_title() +{ + global $page; + + $title = l10n('Albums').' '; + + $is_first = true; + $all_categories = array_merge(array($page['category']), $page['combined_categories']); + foreach ($all_categories as $idx => $category) + { + $title.= $is_first ? '' : ' + '; + $is_first = false; + + $title.= get_cat_display_name(array($category)); + + if (count($all_categories) > 1) // should be always the case + { + $other_cats = $all_categories; + unset($other_cats[$idx]); + + $params = array( + 'category' => array_shift($other_cats), + ); + + if (count($other_cats) > 0) + { + $params['combined_categories'] = $other_cats; + } + $remove_url = make_index_url($params); + + $title.= + 'x' + .'' + .''; + } + } + + return $title; +} + /** * Sets the http status header (200,401,...) * @param int $code @@ -501,6 +550,7 @@ function register_default_menubar_blocks($menu_ref_arr) $menu->register_block( new RegisteredBlock( 'mbTags', 'Related tags', 'piwigo')); $menu->register_block( new RegisteredBlock( 'mbSpecials', 'Specials', 'piwigo')); $menu->register_block( new RegisteredBlock( 'mbMenu', 'Menu', 'piwigo')); + $menu->register_block( new RegisteredBlock( 'mbRelatedCategories', 'Related albums', 'piwigo') ); // We hide the quick identification menu on the identification page. It // would be confusing. diff --git a/include/functions_url.inc.php b/include/functions_url.inc.php index a0637f8c3..0a27d03d3 100644 --- a/include/functions_url.inc.php +++ b/include/functions_url.inc.php @@ -348,6 +348,18 @@ function make_section_in_url($params) { $section_string.= $params['category']['permalink']; } + + if (isset($params['combined_categories'])) + { + foreach ($params['combined_categories'] as $category) + { + $section_string.= '/'.$category['id']; + if ( $conf['category_url_style']=='id-name' ) + { + $section_string.= '-'.str2url($category['name']); + } + } + } } break; @@ -421,13 +433,23 @@ function parse_section_url( $tokens, &$next_token) $page['section'] = 'categories'; $next_token++; - if (isset($tokens[$next_token]) ) + $i = $next_token; + + while (isset($tokens[$next_token])) { if (preg_match('/^(\d+)(?:-(.+))?$/', $tokens[$next_token], $matches)) { if ( isset($matches[2]) ) $page['hit_by']['cat_url_name'] = $matches[2]; - $page['category'] = $matches[1]; + + if (!isset($page['category'])) + { + $page['category'] = $matches[1]; + } + else + { + $page['combined_categories'][] = $matches[1]; + } $next_token++; } else @@ -480,6 +502,24 @@ function parse_section_url( $tokens, &$next_token) } $page['category']=$result; } + + if (isset($page['combined_categories'])) + { + $combined_categories = array(); + + foreach ($page['combined_categories'] as $cat_id) + { + $result = get_cat_info($cat_id); + if (empty($result)) + { + page_not_found(l10n('Requested album does not exist')); + } + + $combined_categories[] = $result; + } + + $page['combined_categories'] = $combined_categories; + } } elseif ( 'tags' == @$tokens[$next_token] ) { diff --git a/include/menubar.inc.php b/include/menubar.inc.php index 7cb65c3e5..46fd265bc 100644 --- a/include/menubar.inc.php +++ b/include/menubar.inc.php @@ -101,6 +101,34 @@ function initialize_menu() $block->template = 'menubar_categories.tpl'; } +//------------------------------------------------------------ related categories + $block = $menu->get_block('mbRelatedCategories'); + + if ($block != null and !empty($page['items'])) + { + $exclude_cat_ids = array(); + if (isset($page['category'])) + { + $exclude_cat_ids = array($page['category']['id']); + if (isset($page['combined_categories'])) + { + foreach ($page['combined_categories'] as $cat) + { + $exclude_cat_ids[] = $cat['id']; + } + } + } + + $block->data = array( + 'MENU_CATEGORIES' => get_related_categories_menu($page['items'], $exclude_cat_ids), + ); + + if (!empty($block->data['MENU_CATEGORIES']) ) + { + $block->template = 'menubar_related_categories.tpl'; + } + } + //------------------------------------------------------------------------ tags $block = $menu->get_block('mbTags'); if ( $block!=null and 'picture' != script_basename() ) diff --git a/include/section_init.inc.php b/include/section_init.inc.php index 586aec7fc..db4c30460 100644 --- a/include/section_init.inc.php +++ b/include/section_init.inc.php @@ -211,7 +211,11 @@ $forbidden = get_sql_condition_FandF( // +-----------------------------------------------------------------------+ if ('categories' == $page['section']) { - if (isset($page['category'])) + if (isset($page['combined_categories'])) + { + $page['title'] = get_combined_categories_content_title(); + } + elseif (isset($page['category'])) { $page = array_merge( $page, @@ -231,7 +235,17 @@ if ('categories' == $page['section']) } // GET IMAGES LIST - if + if (isset($page['combined_categories'])) + { + $cat_ids = array($page['category']['id']); + foreach ($page['combined_categories'] as $category) + { + $cat_ids[] = $category['id']; + } + + $page['items'] = get_image_ids_for_categories($cat_ids); + } + elseif ( $page['startcat'] == 0 and (!isset($page['chronology_field'])) and // otherwise the calendar will requery all subitems diff --git a/language/en_UK/common.lang.php b/language/en_UK/common.lang.php index a6a4a0657..649387757 100644 --- a/language/en_UK/common.lang.php +++ b/language/en_UK/common.lang.php @@ -414,4 +414,5 @@ $lang['Link: %s'] = 'Link: %s'; $lang['Your authentication key is no longer valid.'] = 'Your authentication key is no longer valid.'; $lang['Invalid username or password!'] = 'Invalid username or password!'; $lang['generate random password'] = 'generate random password'; +$lang['Related albums'] = 'Related albums'; ?> diff --git a/language/fr_FR/common.lang.php b/language/fr_FR/common.lang.php index 082d80b2e..96e4861cf 100644 --- a/language/fr_FR/common.lang.php +++ b/language/fr_FR/common.lang.php @@ -412,4 +412,5 @@ $lang['Album name, Z → A'] = 'Nom de l\'album, Z → A'; $lang['Link: %s'] = 'Lien: %s'; $lang['Your authentication key is no longer valid.'] = 'Votre clef d\'identification n\'est plus valide.'; $lang['Invalid username or password!'] = 'Nom d\'utilisateur ou mot de passe invalide !'; -$lang['generate random password'] = 'générer un mot de passe aléatoire'; \ No newline at end of file +$lang['generate random password'] = 'générer un mot de passe aléatoire'; +$lang['Related albums'] = 'Albums liés'; \ No newline at end of file diff --git a/themes/default/template/menubar_related_categories.tpl b/themes/default/template/menubar_related_categories.tpl new file mode 100644 index 000000000..1d1e0837e --- /dev/null +++ b/themes/default/template/menubar_related_categories.tpl @@ -0,0 +1,28 @@ +
+ {'Related albums'|@translate} +
+
+{assign var='ref_level' value=0} +{foreach from=$block->data.MENU_CATEGORIES item=cat} + {if $cat.LEVEL > $ref_level} + '|@str_repeat:($ref_level-$cat.LEVEL)} + {/if} +
  • + {if isset($cat.url)} + {$cat.name} + {else} + {$cat.name} + {/if} + {if $cat.count_images > 0} + {$cat.count_images} + {/if} + {if $cat.count_categories > 0} + {$cat.count_categories} + {/if} + {assign var='ref_level' value=$cat.LEVEL} +{/foreach} +{'
  • '|@str_repeat:$ref_level} +
    diff --git a/themes/default/theme.css b/themes/default/theme.css index 4930d6143..8b1d94944 100644 --- a/themes/default/theme.css +++ b/themes/default/theme.css @@ -113,6 +113,10 @@ padding:0 25px; } +#menubar .badge { + margin-left:5px; +} + .badge::before { content: '['; } @@ -121,6 +125,34 @@ content: ']'; } +.badge.badgeCategories::before { + content:"("; +} +.badge.badgeCategories::after { + content:")"; +} + +/* A nicer (more modern) way to present badge, with a background color and no brackets */ + +/* +.badge::before, .badge::after { + content:none; +} + +.badge { + display: inline-block; + min-width: 5px; + padding: 3px 7px; + color: #eee; + line-height: 1; + vertical-align: middle; + white-space: nowrap; + text-align: center; + background-color: #666; + border-radius: 10px; +} +*/ + /* category and tag results paragraphs on a quick search */ .search_results { font-size: 16px; @@ -796,4 +828,4 @@ LEGEND { #TagsGroupRemoveTag span{ display:none; -} \ No newline at end of file +}