# HG changeset patch # User Dan # Date 1203221251 18000 # Node ID c1c3983496510b450af202eba529cb18fb9c3c53 # Parent ac7d3dedcc4476e39102d5b053b6a7e4cb30f315 Added initial support for notes (aka tags) on images, done completely (including initial load of metadata) with AJAX. They're not fixed at 100x100 like on Facebook either. diff -r ac7d3dedcc44 -r c1c398349651 plugins/Gallery.php --- a/plugins/Gallery.php Sat Nov 24 11:43:34 2007 -0500 +++ b/plugins/Gallery.php Sat Feb 16 23:07:31 2008 -0500 @@ -10,7 +10,7 @@ global $db, $session, $paths, $template, $plugins; // Common objects -define('GALLERY_VERSION', '0.1b1'); +define('GALLERY_VERSION', '0.1b2'); $magick_path = getConfig('imagemagick_path'); if ( !file_exists($magick_path) || !is_executable($magick_path) ) @@ -32,6 +32,7 @@ img_filename varchar(255) NOT NULL, img_time_upload int(12) NOT NULL DEFAULT 0, img_time_mod int(12) NOT NULL DEFAULT 0, + img_tags longtext DEFAULT NULL, PRIMARY KEY ( img_id ) );'); @@ -50,6 +51,13 @@ setConfig('gallery_version', GALLERY_VERSION); } +if ( getConfig('gallery_version') == '0.1b1' ) +{ + $q = $db->sql_query('ALTER TABLE ' . table_prefix . 'gallery ADD COLUMN img_tags longtext DEFAULT NULL'); + if ( !$q ) + $db->_die(); + setConfig('gallery_version', '0.1b2'); +} require( ENANO_ROOT . '/plugins/gallery/functions.php' ); require( ENANO_ROOT . '/plugins/gallery/nssetup.php' ); diff -r ac7d3dedcc44 -r c1c398349651 plugins/gallery/browser.css --- a/plugins/gallery/browser.css Sat Nov 24 11:43:34 2007 -0500 +++ b/plugins/gallery/browser.css Sat Feb 16 23:07:31 2008 -0500 @@ -46,4 +46,23 @@ vertical-align: middle; } +div.snapr_tag_entry { + border: 1px solid #202020; + background-color: #FFFFEB; + color: #101010; + padding: 4px; + -moz-border-radius: 8px; + text-align: left; +} +div.snapr_tag { + border: 1px solid #202020; + background-color: #EBEBFF; + color: #101010; + padding: 4px; + text-align: left; + min-width: 100px; + font-family: arial, helvetica, sans-serif; + font-size: 8pt; +} + diff -r ac7d3dedcc44 -r c1c398349651 plugins/gallery/canvas.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/gallery/canvas.js Sat Feb 16 23:07:31 2008 -0500 @@ -0,0 +1,177 @@ +var canvas_mousemove_temp; +var canvas_keyup_temp; +var CANVAS_KEY_ESC = 27; + +function canvas_click(obj) +{ + var click_x = mouseX - $(obj).Left(); + var click_y = mouseY - $(obj).Top() + getScrollOffset(); + + if ( obj.canvas_in_draw ) + { + canvas_close_draw(obj, click_x, click_y); + } + else + { + canvas_open_draw(obj, click_x, click_y); + } +} + +function canvas_open_draw(obj, x, y) +{ + obj.canvas_box_obj = canvas_create_box(obj, x, y, 1, 1); + obj.canvas_in_draw = true; + obj.onclick = function(e) + { + canvas_click(this); + var onclose = this.getAttribute('canvas:oncomplete'); + if ( onclose ) + { + eval(onclose); + } + } + canvas_replace_mousemove(obj); +} + +function canvas_replace_mousemove(obj) +{ + canvas_mousemove_temp = document.onmousemove; + canvas_mousemove_temp.box_obj = obj; + canvas_keyup_temp = document.onkeyup; + document.onmousemove = function(e) + { + canvas_mousemove_temp(e); + canvas_redraw_box(canvas_mousemove_temp.box_obj); + } + document.onkeyup = function(e) + { + if ( typeof(canvas_keyup_temp) == 'function' ) + canvas_keyup_temp(e); + + if ( e.keyCode == CANVAS_KEY_ESC ) + canvas_cancel_draw(canvas_mousemove_temp.box_obj); + } +} + +function canvas_restore_mousemove() +{ + document.onmousemove = canvas_mousemove_temp; + document.onkeyup = canvas_keyup_temp; +} + +function canvas_create_box(obj, x, y, width, height) +{ + var inner_width = width - 2; + var inner_height = height - 2; + var top = $(obj).Top() + y; + var left = $(obj).Left() + x; + + // draw outer box + var div_outer = document.createElement('div'); + div_outer.className = 'canvasbox'; + div_outer.style.border = '1px solid #000000'; + div_outer.style.position = 'absolute'; + div_outer.style.width = String(width) + 'px'; + div_outer.style.height = String(height) + 'px'; + div_outer.style.top = String(top) + 'px'; + div_outer.style.left = String(left) + 'px'; + + div_outer.rootY = y; + div_outer.rootX = x; + + var div_inner = document.createElement('div'); + div_inner.style.border = '1px solid #FFFFFF'; + if ( IE ) + { + div_inner.style.width = '1px'; + div_inner.style.height = '1px'; + } + else + { + div_inner.style.width = String(inner_width) + 'px'; + div_inner.style.height = String(inner_height) + 'px'; + } + + div_outer.appendChild(div_inner); + + obj.appendChild(div_outer); + return div_outer; +} + +function canvas_redraw_box(obj) +{ + if ( !obj.canvas_box_obj ) + return false; + var rel_x = mouseX - $(obj).Left(); + var rel_y = mouseY - $(obj).Top() + getScrollOffset(); + var new_width = rel_x - obj.canvas_box_obj.rootX; + var new_height = rel_y - obj.canvas_box_obj.rootY; + var rootX = obj.canvas_box_obj.rootX; + var rootY = obj.canvas_box_obj.rootY; + // Limit dimensions to width - origin_x and height - origin_y + if ( new_width + rootX > $(obj).Width() ) + new_width = $(obj).Width() - rootX; + if ( new_height + rootY > $(obj).Height() ) + new_height = $(obj).Height() - rootY; + // If going to the top or left of the origin, avoid negative width/height by moving the box + if ( new_width < 1 ) + { + new_width = rootX - rel_x; + obj.canvas_box_obj.style.left = String(mouseX + 2) + 'px'; + } + if ( new_height < 1 ) + { + new_height = rootY - rel_y; + obj.canvas_box_obj.style.top = String(mouseY + getScrollOffset() + 2) + 'px'; + } + obj.canvas_box_obj.style.width = String(new_width) + 'px'; + obj.canvas_box_obj.style.height = String(new_height) + 'px'; + new_width = new_width - 2; + new_height = new_height - 2; + if ( IE ) + { + var nw = new_width; + var nh = new_height; + obj.canvas_box_obj.firstChild.style.width = String(nw) + 'px'; + obj.canvas_box_obj.firstChild.style.height = String(nh) + 'px'; + } + else + { + obj.canvas_box_obj.firstChild.style.width = String(new_width) + 'px'; + obj.canvas_box_obj.firstChild.style.height = String(new_height) + 'px'; + } +} + +function canvas_close_draw(obj, x, y) +{ + canvas_restore_mousemove(); + obj.canvas_in_draw = false; + obj.canvas = { + top: $(obj.canvas_box_obj).Top() - $(obj).Top(), + left: $(obj.canvas_box_obj).Left() - $(obj).Left(), + width: $(obj.canvas_box_obj).Width(), + height: $(obj.canvas_box_obj).Height() + } + obj.onclick = function(e) + { + canvas_click(this); + } +} + +function canvas_cancel_draw(obj) +{ + canvas_restore_mousemove(); + obj.canvas_in_draw = false; + obj.removeChild(obj.canvas_box_obj); + obj.canvas_box_obj = null; + obj.onclick = function(e) + { + canvas_click(this); + } + var ga = obj.getAttribute('canvas:oncancel'); + if ( ga ) + { + eval(ga); + } +} + diff -r ac7d3dedcc44 -r c1c398349651 plugins/gallery/functions.php --- a/plugins/gallery/functions.php Sat Nov 24 11:43:34 2007 -0500 +++ b/plugins/gallery/functions.php Sat Feb 16 23:07:31 2008 -0500 @@ -314,4 +314,66 @@ return $entries; } +/** + * Wrapper for JSON decoding that works on Enano 1.0.x and 1.1.x + * @param string JSON datastream... + * @return mixed + */ + +function snapr_json_decode($data) +{ + if ( defined('ENANO_ATLEAST_1_1') ) + { + try + { + $decoded = enano_json_decode($data); + } + catch ( Exception $e ) + { + $response = array( + 'mode' => 'error', + 'error' => 'Exception in JSON parser.' + ); + die(enano_json_encode($response)); + } + } + else + { + $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE); + $decoded = $json->decode($data); + } + return ( isset($decoded) ) ? $decoded : false; +} + +/** + * Wrapper for JSON encoding that works on Enano 1.0.x and 1.1.x + * @param mixed Data to encode + * @return string + */ + +function snapr_json_encode($data) +{ + if ( defined('ENANO_ATLEAST_1_1') ) + { + try + { + $encoded = enano_json_encode($data); + } + catch ( Exception $e ) + { + $response = array( + 'mode' => 'error', + 'error' => 'Exception in JSON encoder.' + ); + die(enano_json_encode($response)); + } + } + else + { + $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE); + $encoded = $json->encode($data); + } + return ( isset($encoded) ) ? $encoded : false; +} + ?> diff -r ac7d3dedcc44 -r c1c398349651 plugins/gallery/nssetup.php --- a/plugins/gallery/nssetup.php Sat Nov 24 11:43:34 2007 -0500 +++ b/plugins/gallery/nssetup.php Sat Feb 16 23:07:31 2008 -0500 @@ -21,6 +21,7 @@ $paths->create_namespace('Gallery', 'Image:'); $session->register_acl_type('gal_full_res', AUTH_ALLOW, 'View image at full resolution', array('read'), 'Gallery'); + $session->register_acl_type('snapr_add_tag', AUTH_DISALLOW, 'Add image tags (separate from adding normal tags)', array('read'), 'Gallery'); $session->acl_extend_scope('read', 'Gallery', $paths); $session->acl_extend_scope('post_comments', 'Gallery', $paths); diff -r ac7d3dedcc44 -r c1c398349651 plugins/gallery/src/tag-image.xcf Binary file plugins/gallery/src/tag-image.xcf has changed diff -r ac7d3dedcc44 -r c1c398349651 plugins/gallery/tag-image.gif Binary file plugins/gallery/tag-image.gif has changed diff -r ac7d3dedcc44 -r c1c398349651 plugins/gallery/tagging.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/gallery/tagging.js Sat Feb 16 23:07:31 2008 -0500 @@ -0,0 +1,240 @@ +function snapr_add_tag() +{ + var image = document.getElementById('snapr_preview_img'); + image.parentNode.onclick = function(e) + { + canvas_click(this); + } + image.parentNode.setAttribute('canvas:oncomplete', 'snapr_process_canvas_add(this);'); + image.parentNode.setAttribute('canvas:oncancel', 'obj.onclick = null;'); +} + +function snapr_process_canvas_add(obj) +{ + obj.onclick = null; + var abs_x = $(obj).Left() + obj.canvas.left; + var abs_y = $(obj).Top() + obj.canvas.top; + var height = obj.canvas.height + 2; + + var entry_div = document.createElement('div'); + entry_div.className = 'snapr_tag_entry'; + entry_div.style.position = 'absolute'; + entry_div.style.top = String(abs_y + height) + 'px'; + entry_div.style.left = String(abs_x)+ 'px'; + + entry_div.appendChild(document.createTextNode('Enter a tag:')); + entry_div.appendChild(document.createElement('br')); + + var ta = document.createElement('textarea'); + ta.rows = '7'; + ta.cols = '30'; + entry_div.appendChild(ta); + + entry_div.appendChild(document.createElement('br')); + + var a_add = document.createElement('a'); + a_add.href = '#'; + a_add.onclick = function() + { + snapr_finalize_canvas_add(this.parentNode, this.parentNode.parentNode.canvas, this.previousSibling.previousSibling.value); + return false; + } + a_add.appendChild(document.createTextNode('Add tag')); + entry_div.appendChild(a_add); + + entry_div.appendChild(document.createTextNode(' | ')); + + var a_cancel = document.createElement('a'); + a_cancel.href = '#'; + a_cancel.onclick = function() + { + snapr_finalize_canvas_cancel(this.parentNode); + return false; + } + a_cancel.appendChild(document.createTextNode('Cancel')); + entry_div.appendChild(a_cancel); + + obj.appendChild(entry_div); + ta.focus(); +} + +function snapr_finalize_canvas_add(obj, canvas_data, tag) +{ + // add the new box + var id = obj.parentNode.getAttribute('snapr:imgid'); + if ( !id ) + return false; + + // destroy form, etc. + var parent = obj.parentNode; + parent.removeChild(parent.canvas_box_obj); + parent.removeChild(obj); + + var canvas_json = toJSONString(canvas_data); + ajaxPost(makeUrlNS('Gallery', id), 'ajax=true&act=add_tag&tag=' + escape(tag) + '&canvas_params=' + escape(canvas_json), snapr_process_ajax_tag_packet); +} + +function snapr_finalize_canvas_cancel(obj) +{ + var parent = obj.parentNode; + parent.removeChild(parent.canvas_box_obj); + parent.removeChild(obj); +} + +function snapr_draw_note(obj, tag, canvas_data, note_id, initial_hide, auth_delete) +{ + var newbox = canvas_create_box(obj, canvas_data.left, canvas_data.top, canvas_data.width, canvas_data.height); + newbox.tag_id = note_id; + obj.onmouseover = function() + { + var boxen = this.getElementsByTagName('div'); + for ( var i = 0; i < boxen.length; i++ ) + if ( boxen[i].className == 'canvasbox' ) + boxen[i].style.display = 'block'; + } + obj.onmouseout = function() + { + var boxen = this.getElementsByTagName('div'); + for ( var i = 0; i < boxen.length; i++ ) + if ( boxen[i].className == 'canvasbox' ) + boxen[i].style.display = 'none'; + } + newbox.onmouseover = function() + { + this.style.borderColor = '#FFFF00'; + this.firstChild.style.borderColor = '#000000'; + snapr_display_note(this.noteObj); + } + newbox.onmouseout = function() + { + this.style.borderColor = '#000000'; + this.firstChild.style.borderColor = '#FFFFFF'; + snapr_hide_note(this.noteObj); + } + if ( auth_delete ) + { + var p = document.createElement('p'); + p.style.cssFloat = 'right'; + p.style.styleFloat = 'right'; + p.style.fontWeight = 'bold'; + p.style.margin = '5px'; + var a_del = document.createElement('a'); + a_del.style.color = '#FF0000'; + a_del.href = '#'; + a_del.onclick = function() + { + snapr_nuke_tag(this.parentNode.parentNode.parentNode); + return false; + } + a_del.appendChild(document.createTextNode('[X]')); + p.appendChild(a_del); + newbox.firstChild.appendChild(p); + } + var abs_x = $(newbox).Left(); + var abs_y = $(newbox).Top() + $(newbox).Height() + 2; + var noteObj = document.createElement('div'); + newbox.noteObj = noteObj; + noteObj.className = 'snapr_tag'; + noteObj.style.display = 'none'; + noteObj.style.position = 'absolute'; + noteObj.style.top = abs_y + 'px'; + noteObj.style.left = abs_x + 'px'; + var re = new RegExp(unescape('%0A'), 'g'); + noteObj.innerHTML = tag.replace(re, "
\n"); + obj.appendChild(noteObj); + if ( initial_hide ) + newbox.style.display = 'none'; +} + +function snapr_display_note(note) +{ + //domObjChangeOpac(0, note); + note.style.display = 'block'; + //domOpacity(note, 0, 100, 500); +} + +function snapr_hide_note(note) +{ + //domOpacity(note, 100, 0, 500); + //setTimeout(function() + // { + note.style.display = 'none'; + // }, 600); +} + +function snapr_nuke_tag(obj) +{ + // add the new box + var parent_obj = document.getElementById('snapr_preview_img').parentNode; + var id = parent_obj.getAttribute('snapr:imgid'); + if ( !id ) + return false; + ajaxPost(makeUrlNS('Gallery', id), 'ajax=true&act=del_tag&tag_id=' + obj.tag_id, snapr_process_ajax_tag_packet); +} + +function snapr_process_ajax_tag_packet() +{ + if ( ajax.readyState == 4 && ajax.status == 200 ) + { + var response = String(ajax.responseText + ''); + if ( response.substr(0, 1) != '[' && response.substr(0, 1) != '{' ) + { + handle_invalid_json(response); + return false; + } + response = parseJSON(response); + if ( response.mode ) + { + if ( response.mode == 'error' ) + { + alert(response.error); + return false; + } + } + var parent_obj = document.getElementById('snapr_preview_img').parentNode; + for ( var i = 0; i < response.length; i++ ) + { + var packet = response[i]; + switch(packet.mode) + { + case 'add': + snapr_draw_note(parent_obj, packet.tag, packet.canvas_data, packet.note_id, packet.initial_hide, packet.auth_delete); + break; + case 'remove': + // Server requested to remove a tag + var divs = parent_obj.getElementsByTagName('div'); + for ( var i = 0; i < divs.length; i++ ) + { + var box = divs[i]; + if ( box.className == 'canvasbox' ) + { + if ( box.tag_id == packet.note_id ) + { + // You. We have orders to shoot. Stand in front of wall. + var sibling = box.nextSibling; + var parent = box.parentNode; + // BLAM. + parent.removeChild(sibling); + parent.removeChild(box); + break; + } + } + } + break; + } + } + } +} + +var snapr_tags_onload = function() +{ + // add the new box + var parent_obj = document.getElementById('snapr_preview_img').parentNode; + var id = parent_obj.getAttribute('snapr:imgid'); + if ( !id ) + return false; + ajaxPost(makeUrlNS('Gallery', id), 'ajax=true&act=get_tags', snapr_process_ajax_tag_packet); +} + +addOnloadHook(snapr_tags_onload); + diff -r ac7d3dedcc44 -r c1c398349651 plugins/gallery/viewimage.php --- a/plugins/gallery/viewimage.php Sat Nov 24 11:43:34 2007 -0500 +++ b/plugins/gallery/viewimage.php Sat Feb 16 23:07:31 2008 -0500 @@ -36,7 +36,7 @@ $img_id = intval($page->page_id); if ( !$img_id ) return false; - $q = $db->sql_query('SELECT img_id, img_title, img_desc, print_sizes, img_time_upload, img_time_mod, img_filename, folder_parent FROM '.table_prefix.'gallery WHERE img_id=' . $img_id . ';'); + $q = $db->sql_query('SELECT img_id, img_title, img_desc, print_sizes, img_time_upload, img_time_mod, img_filename, folder_parent, img_tags FROM '.table_prefix.'gallery WHERE img_id=' . $img_id . ';'); if ( !$q ) $db->_die(); } @@ -61,7 +61,7 @@ $folders = array_reverse($folders); // This is one of the best MySQL tricks on the market. We're going to reverse-travel a folder path using LEFT JOIN and the incredible power of metacoded SQL - $sql = 'SELECT g0.img_id, g0.img_title, g0.img_desc, g0.print_sizes, g0.img_time_upload, g0.img_time_mod, g0.img_filename, g0.folder_parent FROM '.table_prefix.'gallery AS g0'; + $sql = 'SELECT g0.img_id, g0.img_title, g0.img_desc, g0.print_sizes, g0.img_time_upload, g0.img_time_mod, g0.img_filename, g0.folder_parent, g0.img_tags FROM '.table_prefix.'gallery AS g0'; $where = "\n " . 'WHERE g0.img_title=\'' . $db->escape($folders[0]) . '\''; foreach ( $folders as $i => $folder ) { @@ -166,11 +166,102 @@ $db->free_result(); + $perms = $session->fetch_page_acl(strval($img_id), 'Gallery'); + + if ( isset($_POST['ajax']) && @$_POST['ajax'] === 'true' && isset($_POST['act']) ) + { + $mode =& $_POST['act']; + $response = array(); + switch($mode) + { + case 'add_tag': + if ( !$perms->get_permissions('snapr_add_tag') ) + { + die(snapr_json_encode(array( + 'mode' => 'error', + 'error' => 'You don\'t have permission to add tags.' + ))); + } + if ( empty($row['img_tags']) ) + { + $row['img_tags'] = '[]'; + } + $row['img_tags'] = snapr_json_decode($row['img_tags']); + + $canvas_data = snapr_json_decode($_POST['canvas_params']); + $tag_data = array( + 'tag' => sanitize_html($_POST['tag']), + 'canvas_data' => $canvas_data + ); + $row['img_tags'][] = $tag_data; + $tag_data['note_id'] = count($row['img_tags']) - 1; + $tag_data['mode'] = 'add'; + $tag_data['initial_hide'] = false; + $tag_data['auth_delete'] = true; + + $row['img_tags'] = snapr_json_encode($row['img_tags']); + $row['img_tags'] = $db->escape($row['img_tags']); + $q = $db->sql_query('UPDATE ' . table_prefix . "gallery SET img_tags = '{$row['img_tags']}' WHERE img_id = $img_id;"); + if ( !$q ) + $db->die_json(); + + $response[] = $tag_data; + break; + case 'del_tag': + if ( !$perms->get_permissions('snapr_add_tag') ) + { + die(snapr_json_encode(array( + 'mode' => 'error', + 'error' => 'You don\'t have permission to add tags.' + ))); + } + if ( empty($row['img_tags']) ) + { + $row['img_tags'] = '[]'; + } + $row['img_tags'] = snapr_json_decode($row['img_tags']); + + $tag_id = intval(@$_POST['tag_id']); + if ( isset($row['img_tags'][$tag_id]) ) + unset($row['img_tags'][$tag_id]); + + $row['img_tags'] = snapr_json_encode($row['img_tags']); + $row['img_tags'] = $db->escape($row['img_tags']); + $q = $db->sql_query('UPDATE ' . table_prefix . "gallery SET img_tags = '{$row['img_tags']}' WHERE img_id = $img_id;"); + if ( !$q ) + $db->die_json(); + + $response[] = array( + 'mode' => 'remove', + 'note_id' => $tag_id + ); + break; + case 'get_tags': + $response = snapr_json_decode($row['img_tags']); + foreach ( $response as $key => $_ ) + { + unset($_); + $tag =& $response[$key]; + $tag['note_id'] = $key; + $tag['mode'] = 'add'; + $tag['initial_hide'] = true; + $tag['auth_delete'] = $perms->get_permissions('snapr_add_tag'); + } + unset($tag); + break; + } + echo snapr_json_encode($response); + return true; + } + + $have_notes = ( empty($row['img_tags']) ) ? false : ( count(snapr_json_decode($row['img_tags'])) > 0 ); + + $template->add_header(''); + $template->add_header(''); + $template->tpl_strings['PAGE_NAME'] = 'Gallery image: ' . htmlspecialchars($row['img_title']); $title_spacey = strtolower(htmlspecialchars($row['img_title'])); - $perms = $session->fetch_page_acl(strval($img_id), 'Gallery'); - $template->header(); $img_id = intval($img_id); @@ -198,17 +289,7 @@ $img_url = makeUrlNS('Special', 'GalleryFetcher/preview/' . $img_id); $img_href = makeUrlNS('Special', 'GalleryFetcher/full/' . $img_id); - if ( $perms->get_permissions('gal_full_res') ) - { - echo ''; - } - - echo 'Image preview (640px max width)'; - - if ( $perms->get_permissions('gal_full_res') ) - { - echo ''; - } + echo '
Image preview (640px max width)
'; echo ''; echo ''; + if ( $perms->get_permissions('gal_full_res') || $have_notes ) + { + echo ''; + } echo '
'; @@ -235,12 +316,32 @@ echo '
' . "image $folder_this of $folder_total" . '
'; + + if ( $perms->get_permissions('gal_full_res') ) + echo "View in original resolution"; + + if ( $perms->get_permissions('gal_full_res') && $have_notes ) + echo ' :: '; + + if ( $have_notes ) + echo 'Mouse over photo to view tags'; + + echo '
'; echo ''; - if ( $session->user_level >= USER_LEVEL_ADMIN ) + if ( $session->user_level >= USER_LEVEL_ADMIN || $perms->get_permissions('snapr_add_tag') ) { - echo '
[ edit image ]
'; + echo '
'; + if ( $session->user_level >= USER_LEVEL_ADMIN ) + echo '[ edit image ] '; + if ( $perms->get_permissions('snapr_add_tag') ) + echo '[  add a tag ] '; + echo '
'; } if ( !empty($row['img_desc']) )