diff options
author | plegall <plg@piwigo.org> | 2016-01-06 14:41:25 +0100 |
---|---|---|
committer | plegall <plg@piwigo.org> | 2016-01-06 14:41:25 +0100 |
commit | 646aa6f19a45618abaf35f1b889d421e9c184bc9 (patch) | |
tree | 96eff284a7f90fc24327cea3a9714a6b6e79365d | |
parent | aa581bd3dec54984845096e3a73d1ce72c6922bb (diff) | |
parent | 426e10e235689211fc52ee0077dce32ea3124bd6 (diff) |
Merge branch 'feature/392-auth-keys'
Diffstat (limited to '')
-rw-r--r-- | admin/album_notification.php | 26 | ||||
-rw-r--r-- | admin/include/functions_notification_by_mail.inc.php | 14 | ||||
-rw-r--r-- | admin/notification_by_mail.php | 18 | ||||
-rw-r--r-- | include/config_default.inc.php | 4 | ||||
-rw-r--r-- | include/constants.php | 2 | ||||
-rw-r--r-- | include/functions.inc.php | 2 | ||||
-rw-r--r-- | include/functions_html.inc.php | 16 | ||||
-rw-r--r-- | include/functions_mail.inc.php | 49 | ||||
-rw-r--r-- | include/functions_notification.inc.php | 67 | ||||
-rw-r--r-- | include/functions_user.inc.php | 135 | ||||
-rw-r--r-- | include/user.inc.php | 6 | ||||
-rw-r--r-- | install/db/147-database.php | 46 | ||||
-rw-r--r-- | install/db/148-database.php | 39 | ||||
-rw-r--r-- | themes/default/template/mail/text/html/cat_group_info.tpl | 2 |
14 files changed, 372 insertions, 54 deletions
diff --git a/admin/album_notification.php b/admin/album_notification.php index cafaad170..4dd578b1a 100644 --- a/admin/album_notification.php +++ b/admin/album_notification.php @@ -54,6 +54,8 @@ if (isset($_POST['submitEmail']) and !empty($_POST['group'])) is empty find child representative_picture_id */ if (!empty($category['representative_picture_id'])) { + $img = array(); + $query = ' SELECT id, file, path, representative_ext FROM '.IMAGES_TABLE.' @@ -65,21 +67,19 @@ SELECT id, file, path, representative_ext { $element = pwg_db_fetch_assoc($result); - $img_url = '<a href="'. - make_picture_url(array( - 'image_id' => $element['id'], - 'image_file' => $element['file'], - 'category' => $category - )) - .'" class="thumblnk"><img src="'.DerivativeImage::url(IMG_THUMB, $element).'"></a>'; + $img = array( + 'link' => make_picture_url( + array( + 'image_id' => $element['id'], + 'image_file' => $element['file'], + 'category' => $category + ) + ), + 'src' => DerivativeImage::url(IMG_THUMB, $element), + ); } } - if (!isset($img_url)) - { - $img_url = ''; - } - pwg_mail_group( $_POST['group'], array( @@ -90,7 +90,7 @@ SELECT id, file, path, representative_ext array( 'filename' => 'cat_group_info', 'assign' => array( - 'IMG_URL' => $img_url, + 'IMG' => $img, 'CAT_NAME' => trigger_change('render_category_name', $category['name'], 'admin_cat_list'), 'LINK' => make_index_url(array( 'category' => array( diff --git a/admin/include/functions_notification_by_mail.inc.php b/admin/include/functions_notification_by_mail.inc.php index 8d0fe2621..7dc113b0f 100644 --- a/admin/include/functions_notification_by_mail.inc.php +++ b/admin/include/functions_notification_by_mail.inc.php @@ -125,12 +125,12 @@ select U.'.$conf['user_fields']['username'].' as username, U.'.$conf['user_fields']['email'].' as mail_address, N.enabled, - N.last_send -from - '.USER_MAIL_NOTIFICATION_TABLE.' as N, - '.USERS_TABLE.' as U -where - N.user_id = U.'.$conf['user_fields']['id']; + N.last_send, + UI.status +from '.USER_MAIL_NOTIFICATION_TABLE.' as N + JOIN '.USERS_TABLE.' as U on N.user_id = U.'.$conf['user_fields']['id'].' + JOIN '.USER_INFOS_TABLE.' as UI on UI.user_id = N.user_id +where 1=1'; if ($action == 'send') { @@ -159,7 +159,7 @@ order by'; else { $query .= ' - username;'; + username'; } $query .= ';'; diff --git a/admin/notification_by_mail.php b/admin/notification_by_mail.php index 38cadff6c..f146ba30f 100644 --- a/admin/notification_by_mail.php +++ b/admin/notification_by_mail.php @@ -289,13 +289,24 @@ function do_action_send_mail_notification($action = 'list_to_send', $check_key_l if ($is_action_send) { + $auth = null; + $add_url_params = array(); + + $auth_key = create_user_auth_key($nbm_user['user_id'], $nbm_user['status']); + + if ($auth_key !== false) + { + $auth = $auth_key['auth_key']; + $add_url_params['auth'] = $auth; + } + set_make_full_url(); // Fill return list of "treated" check_key for 'send' $return_list[] = $nbm_user['check_key']; if ($conf['nbm_send_detailed_content']) { - $news = news($nbm_user['last_send'], $dbnow, false, $conf['nbm_send_html_mail']); + $news = news($nbm_user['last_send'], $dbnow, false, $conf['nbm_send_html_mail'], $auth); $exist_data = count($news) > 0; } else @@ -362,7 +373,7 @@ function do_action_send_mail_notification($action = 'list_to_send', $check_key_l array ( 'TITLE' => get_title_recent_post_date($date_detail), - 'HTML_DATA' => get_html_description_recent_post_date($date_detail) + 'HTML_DATA' => get_html_description_recent_post_date($date_detail, $auth) ) ); } @@ -373,7 +384,7 @@ function do_action_send_mail_notification($action = 'list_to_send', $check_key_l array ( 'GOTO_GALLERY_TITLE' => $conf['gallery_title'], - 'GOTO_GALLERY_URL' => get_gallery_home_url(), + 'GOTO_GALLERY_URL' => add_url_params(get_gallery_home_url(), $add_url_params), 'SEND_AS_NAME' => $env_nbm['send_as_name'], ) ); @@ -389,6 +400,7 @@ function do_action_send_mail_notification($action = 'list_to_send', $check_key_l 'email_format' => $env_nbm['email_format'], 'content' => $env_nbm['mail_template']->parse('notification_by_mail', true), 'content_format' => $env_nbm['email_format'], + 'auth_key' => $auth, ) ); diff --git a/include/config_default.inc.php b/include/config_default.inc.php index eafb9d5a9..2de75764d 100644 --- a/include/config_default.inc.php +++ b/include/config_default.inc.php @@ -646,6 +646,10 @@ $conf['recent_post_dates'] = array( // the author shown in the RSS feed <author> element $conf['rss_feed_author'] = 'Piwigo notifier'; +// how long does the authentication key stays valid, in seconds. 3 days by +// default. 0 to disable. +$conf['auth_key_duration'] = 3*24*60*60; + // +-----------------------------------------------------------------------+ // | Set admin layout | // +-----------------------------------------------------------------------+ diff --git a/include/constants.php b/include/constants.php index ef321a4bc..f9a032d0f 100644 --- a/include/constants.php +++ b/include/constants.php @@ -81,6 +81,8 @@ if (!defined('USER_FEED_TABLE')) define('USER_FEED_TABLE', $prefixeTable.'user_feed'); if (!defined('RATE_TABLE')) define('RATE_TABLE', $prefixeTable.'rate'); +if (!defined('USER_AUTH_KEYS_TABLE')) + define('USER_AUTH_KEYS_TABLE', $prefixeTable.'user_auth_keys'); if (!defined('USER_CACHE_TABLE')) define('USER_CACHE_TABLE', $prefixeTable.'user_cache'); if (!defined('USER_CACHE_CATEGORIES_TABLE')) diff --git a/include/functions.inc.php b/include/functions.inc.php index 2119abe8f..578830ba5 100644 --- a/include/functions.inc.php +++ b/include/functions.inc.php @@ -446,6 +446,7 @@ INSERT INTO '.HISTORY_TABLE.' image_id, image_type, format_id, + auth_key_id, tag_ids ) VALUES @@ -459,6 +460,7 @@ INSERT INTO '.HISTORY_TABLE.' '.(isset($image_id) ? $image_id : 'NULL').', '.(isset($image_type) ? "'".$image_type."'" : 'NULL').', '.(isset($format_id) ? $format_id : 'NULL').', + '.(isset($page['auth_key_id']) ? $page['auth_key_id'] : 'NULL').', '.(isset($tags_string) ? "'".$tags_string."'" : 'NULL').' ) ;'; diff --git a/include/functions_html.inc.php b/include/functions_html.inc.php index 8668e68ad..59861c46d 100644 --- a/include/functions_html.inc.php +++ b/include/functions_html.inc.php @@ -103,10 +103,17 @@ function get_cat_display_name($cat_informations, $url='') function get_cat_display_name_cache($uppercats, $url = '', $single_link = false, - $link_class = null) + $link_class = null, + $auth_key=null) { global $cache, $conf; + $add_url_params = array(); + if (isset($auth_key)) + { + $add_url_params['auth'] = $auth_key; + } + if (!isset($cache['cat_names'])) { $query = ' @@ -119,7 +126,7 @@ SELECT id, name, permalink $output = ''; if ($single_link) { - $single_url = get_root_url().$url.array_pop(explode(',', $uppercats)); + $single_url = add_url_params(get_root_url().$url.array_pop(explode(',', $uppercats)), $add_url_params); $output.= '<a href="'.$single_url.'"'; if (isset($link_class)) { @@ -155,10 +162,13 @@ SELECT id, name, permalink { $output.= ' <a href="' - .make_index_url( + .add_url_params( + make_index_url( array( 'category' => $cat, ) + ), + $add_url_params ) .'">'.$cat['name'].'</a>'; } diff --git a/include/functions_mail.inc.php b/include/functions_mail.inc.php index ed1081713..67be16c15 100644 --- a/include/functions_mail.inc.php +++ b/include/functions_mail.inc.php @@ -514,6 +514,8 @@ SELECT DISTINCT language // get subset of users in this group for a specific language $query = ' SELECT + ui.user_id, + ui.status, u.'.$conf['user_fields']['username'].' AS name, u.'.$conf['user_fields']['email'].' AS email FROM '.USER_GROUP_TABLE.' AS ug @@ -534,13 +536,33 @@ SELECT switch_lang_to($language); - $return&= pwg_mail(null, - array_merge( - $args, - array('Bcc' => $users) - ), - $tpl - ); + foreach ($users as $u) + { + $authkey = create_user_auth_key($u['user_id'], $u['status']); + + $user_tpl = $tpl; + + if ($authkey !== false) + { + $user_tpl['assign']['LINK'] = add_url_params($tpl['assign']['LINK'], array('auth' => $authkey['auth_key'])); + + if (isset($user_tpl['assign']['IMG']['link'])) + { + $user_tpl['assign']['IMG']['link'] = add_url_params( + $user_tpl['assign']['IMG']['link'], + array('auth' => $authkey['auth_key']) + ); + } + } + + $user_args = $args; + if ($authkey !== false) + { + $user_args['auth_key'] = $authkey['auth_key']; + } + + $return &= pwg_mail($u['email'], $user_args, $user_tpl); + } switch_lang_back(); } @@ -563,6 +585,7 @@ SELECT * o theme: theme to use [default value $conf_mail['mail_theme']] * o mail_title: main title of the mail [default value $conf['gallery_title']] * o mail_subtitle: subtitle of the mail [default value subject] + * o auth_key: authentication key to add on footer link [default value null] * @param array $tpl - use these options to define a custom content template file * o filename * o dirname (optional) @@ -695,6 +718,10 @@ function pwg_mail($to, $args=array(), $tpl=array()) { // key compose of indexes witch allow to cache mail data $cache_key = $content_type.'-'.$lang_info['code']; + if (!empty($args['auth_key'])) + { + $cache_key.= '-'.$args['auth_key']; + } if (!isset($conf_mail[$cache_key])) { @@ -709,9 +736,15 @@ function pwg_mail($to, $args=array(), $tpl=array()) $template->set_filename('mail_header', 'header.tpl'); $template->set_filename('mail_footer', 'footer.tpl'); + $add_url_params = array(); + if (!empty($args['auth_key'])) + { + $add_url_params['auth'] = $args['auth_key']; + } + $template->assign( array( - 'GALLERY_URL' => get_gallery_home_url(), + 'GALLERY_URL' => add_url_params(get_gallery_home_url(), $add_url_params), 'GALLERY_TITLE' => isset($page['gallery_title']) ? $page['gallery_title'] : $conf['gallery_title'], 'VERSION' => $conf['show_version'] ? PHPWG_VERSION : '', 'PHPWG_URL' => defined('PHPWG_URL') ? PHPWG_URL : '', diff --git a/include/functions_notification.inc.php b/include/functions_notification.inc.php index bc4d1a374..c7bbb66b3 100644 --- a/include/functions_notification.inc.php +++ b/include/functions_notification.inc.php @@ -395,27 +395,45 @@ function add_news_line(&$news, $count, $singular_key, $plural_key, $url='', $add * @param bool $add_url add html link around news * @return array */ -function news($start=null, $end=null, $exclude_img_cats=false, $add_url=false) +function news($start=null, $end=null, $exclude_img_cats=false, $add_url=false, $auth_key=null) { $news = array(); - if (!$exclude_img_cats) + $add_url_params = array(); + if (isset($auth_key)) { - add_news_line( $news, - nb_new_elements($start, $end), '%d new photo', '%d new photos', - make_index_url(array('section'=>'recent_pics')), $add_url ); + $add_url_params['auth'] = $auth_key; } if (!$exclude_img_cats) { - add_news_line( $news, - nb_updated_categories($start, $end), '%d album updated', '%d albums updated', - make_index_url(array('section'=>'recent_cats')), $add_url ); + add_news_line( + $news, + nb_new_elements($start, $end), + '%d new photo', + '%d new photos', + add_url_params(make_index_url(array('section'=>'recent_pics')), $add_url_params), + $add_url + ); + + add_news_line( + $news, + nb_updated_categories($start, $end), + '%d album updated', + '%d albums updated', + add_url_params(make_index_url(array('section'=>'recent_cats')), $add_url_params), + $add_url + ); } - add_news_line( $news, - nb_new_comments($start, $end), '%d new comment', '%d new comments', - get_root_url().'comments.php', $add_url ); + add_news_line( + $news, + nb_new_comments($start, $end), + '%d new comment', + '%d new comments', + add_url_params(get_root_url().'comments.php', $add_url_params), + $add_url + ); if (is_admin()) { @@ -527,17 +545,23 @@ function get_recent_post_dates_array($args) * @param array $date_detail returned value of get_recent_post_dates() * @return string */ -function get_html_description_recent_post_date($date_detail) +function get_html_description_recent_post_date($date_detail, $auth_key=null) { global $conf; + $add_url_params = array(); + if (isset($auth_key)) + { + $add_url_params['auth'] = $auth_key; + } + $description = '<ul>'; $description .= '<li>' .l10n_dec('%d new photo', '%d new photos', $date_detail['nb_elements']) .' (' - .'<a href="'.make_index_url(array('section'=>'recent_pics')).'">' + .'<a href="'.add_url_params(make_index_url(array('section'=>'recent_pics')), $add_url_params).'">' .l10n('Recent photos').'</a>' .')' .'</li><br>'; @@ -546,11 +570,16 @@ function get_html_description_recent_post_date($date_detail) { $tn_src = DerivativeImage::thumb_url($element); $description .= '<a href="'. - make_picture_url(array( - 'image_id' => $element['id'], - 'image_file' => $element['file'], - )) - .'"><img src="'.$tn_src.'"></a>'; + add_url_params( + make_picture_url( + array( + 'image_id' => $element['id'], + 'image_file' => $element['file'], + ) + ), + $add_url_params + ) + .'"><img src="'.$tn_src.'"></a>'; } $description .= '...<br>'; @@ -564,7 +593,7 @@ function get_html_description_recent_post_date($date_detail) { $description .= '<li>' - .get_cat_display_name_cache($cat['uppercats']) + .get_cat_display_name_cache($cat['uppercats'],'', false, null, $auth_key) .' ('. l10n_dec('%d new photo', '%d new photos', $cat['img_count']).')' .'</li>'; diff --git a/include/functions_user.inc.php b/include/functions_user.inc.php index 5f503b36e..cd186183a 100644 --- a/include/functions_user.inc.php +++ b/include/functions_user.inc.php @@ -1462,4 +1462,139 @@ function get_recent_photos_sql($db_field) .pwg_db_get_recent_period_expression($user['recent_period']) .','.pwg_db_get_recent_period_expression(1,$user['last_photo_date']).')'; } + +/** + * Performs auto-connection if authentication key is valid. + * + * @since 2.8 + * + * @return bool + */ +function auth_key_login($auth_key) +{ + global $conf, $user, $page; + + if ($user['id'] != $conf['guest_id']) + { + return false; + } + + if (!preg_match('/^[a-z0-9]{30}$/i', $auth_key)) + { + return false; + } + + $query = ' +SELECT + *, + '.$conf['user_fields']['username'].' AS username, + NOW() AS dbnow + FROM '.USER_AUTH_KEYS_TABLE.' AS uak + JOIN '.USER_INFOS_TABLE.' AS ui ON uak.user_id = ui.user_id + JOIN '.USERS_TABLE.' AS u ON u.'.$conf['user_fields']['id'].' = ui.user_id + WHERE auth_key = \''.$auth_key.'\' +;'; + $keys = query2array($query); + + if (count($keys) == 0) + { + return false; + } + + $key = $keys[0]; + + // is the key still valid? + if (strtotime($key['expired_on']) < strtotime($key['dbnow'])) + { + return false; + } + + // admin/webmaster/guest can't get connected with authentication keys + if (!in_array($key['status'], array('normal','generic'))) + { + return false; + } + + $user['id'] = $key['user_id']; + log_user($user['id'], false); + trigger_notify('login_success', stripslashes($key['username'])); + + // to be registered in history table by pwg_log function + $page['auth_key_id'] = $key['auth_key_id']; + + return true; +} + +/** + * Creates an authentication key. + * + * @since 2.8 + * @param int $user_id + * @return array + */ +function create_user_auth_key($user_id, $user_status=null) +{ + global $conf; + + if (0 == $conf['auth_key_duration']) + { + return false; + } + + if (!isset($user_status)) + { + // we have to find the user status + $query = ' +SELECT + status + FROM '.USER_INFOS_TABLE.' + WHERE user_id = '.$user_id.' +;'; + $user_infos = query2array($query); + + if (count($user_infos) == 0) + { + return false; + } + + $user_status = $user_infos[0]['status']; + } + + if (!in_array($user_status, array('normal','generic'))) + { + return false; + } + + $candidate = generate_key(30); + + $query = ' +SELECT + COUNT(*), + NOW(), + ADDDATE(NOW(), INTERVAL '.$conf['auth_key_duration'].' SECOND) + FROM '.USER_AUTH_KEYS_TABLE.' + WHERE auth_key = \''.$candidate.'\' +;'; + list($counter, $now, $expiration) = pwg_db_fetch_row(pwg_query($query)); + if (0 == $counter) + { + $key = array( + 'auth_key' => $candidate, + 'user_id' => $user_id, + 'created_on' => $now, + 'duration' => $conf['auth_key_duration'], + 'expired_on' => $expiration, + ); + + single_insert(USER_AUTH_KEYS_TABLE, $key); + + $key['auth_key_id'] = pwg_db_insert_id(); + + return $key; + } + else + { + return create_user_auth_key($user_id, $user_status); + } +} ?>
\ No newline at end of file diff --git a/include/user.inc.php b/include/user.inc.php index 4de5cc6c3..c02fcb0ac 100644 --- a/include/user.inc.php +++ b/include/user.inc.php @@ -65,6 +65,12 @@ if ($conf['apache_authentication']) } } +// automatic login by authentication key +if (isset($_GET['auth'])) +{ + auth_key_login($_GET['auth']); +} + $user = build_user( $user['id'], ( defined('IN_ADMIN') and IN_ADMIN ) ? false : true // use cache ? ); diff --git a/install/db/147-database.php b/install/db/147-database.php new file mode 100644 index 000000000..4c98c66c3 --- /dev/null +++ b/install/db/147-database.php @@ -0,0 +1,46 @@ +<?php +// +-----------------------------------------------------------------------+ +// | Piwigo - a PHP based photo gallery | +// +-----------------------------------------------------------------------+ +// | Copyright(C) 2008-2015 Piwigo Team http://piwigo.org | +// | Copyright(C) 2003-2008 PhpWebGallery Team http://phpwebgallery.net | +// | Copyright(C) 2002-2003 Pierrick LE GALL http://le-gall.net/pierrick | +// +-----------------------------------------------------------------------+ +// | This program is free software; you can redistribute it and/or modify | +// | it under the terms of the GNU General Public License as published by | +// | the Free Software Foundation | +// | | +// | This program is distributed in the hope that it will be useful, but | +// | WITHOUT ANY WARRANTY; without even the implied warranty of | +// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | +// | General Public License for more details. | +// | | +// | You should have received a copy of the GNU General Public License | +// | along with this program; if not, write to the Free Software | +// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | +// | USA. | +// +-----------------------------------------------------------------------+ + +if (!defined('PHPWG_ROOT_PATH')) +{ + die('Hacking attempt!'); +} + +$upgrade_description = 'add user authentication keys table'; + +// we use PREFIX_TABLE, in case Piwigo uses an external user table +pwg_query(' +CREATE TABLE `'.PREFIX_TABLE.'user_auth_keys` ( + `auth_key_id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `auth_key` varchar(255) NOT NULL, + `user_id` mediumint(8) unsigned NOT NULL, + `created_on` datetime NOT NULL, + `duration` int(11) unsigned DEFAULT NULL, + `expired_on` datetime NOT NULL, + PRIMARY KEY (`auth_key_id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 +;'); + +echo "\n".$upgrade_description."\n"; + +?> diff --git a/install/db/148-database.php b/install/db/148-database.php new file mode 100644 index 000000000..79edebdbc --- /dev/null +++ b/install/db/148-database.php @@ -0,0 +1,39 @@ +<?php +// +-----------------------------------------------------------------------+ +// | Piwigo - a PHP based photo gallery | +// +-----------------------------------------------------------------------+ +// | Copyright(C) 2008-2015 Piwigo Team http://piwigo.org | +// | Copyright(C) 2003-2008 PhpWebGallery Team http://phpwebgallery.net | +// | Copyright(C) 2002-2003 Pierrick LE GALL http://le-gall.net/pierrick | +// +-----------------------------------------------------------------------+ +// | This program is free software; you can redistribute it and/or modify | +// | it under the terms of the GNU General Public License as published by | +// | the Free Software Foundation | +// | | +// | This program is distributed in the hope that it will be useful, but | +// | WITHOUT ANY WARRANTY; without even the implied warranty of | +// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | +// | General Public License for more details. | +// | | +// | You should have received a copy of the GNU General Public License | +// | along with this program; if not, write to the Free Software | +// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | +// | USA. | +// +-----------------------------------------------------------------------+ + +if (!defined('PHPWG_ROOT_PATH')) +{ + die('Hacking attempt!'); +} + +$upgrade_description = 'add auth_key_id in history table'; + +// we use PREFIX_TABLE, in case Piwigo uses an external user table +pwg_query(' +ALTER TABLE `'.PREFIX_TABLE.'history` + ADD COLUMN `auth_key_id` int(11) unsigned DEFAULT NULL +;'); + +echo "\n".$upgrade_description."\n"; + +?> diff --git a/themes/default/template/mail/text/html/cat_group_info.tpl b/themes/default/template/mail/text/html/cat_group_info.tpl index e8d7d7c10..6a136c63c 100644 --- a/themes/default/template/mail/text/html/cat_group_info.tpl +++ b/themes/default/template/mail/text/html/cat_group_info.tpl @@ -1,6 +1,6 @@ <div id="cat_group_info"> <h2>{'Informations'|@translate}</h2> -<p>{$IMG_URL}</p> +<p><a href="{$IMG.link}" class="thumblnk"><img src="{$IMG.src}"></a></p> <p>{'Hello,'|@translate}</p> <p>{'Discover album:'|@translate} <a href="{$LINK}">{$CAT_NAME}</a></p> <p>{$CPL_CONTENT}</p> |