diff --git a/boot.php b/boot.php index 96dee2c46..74e77caa1 100755 --- a/boot.php +++ b/boot.php @@ -49,7 +49,7 @@ define ( 'RED_PLATFORM', 'redmatrix' ); define ( 'RED_VERSION', trim(file_get_contents('version.inc')) . 'R'); define ( 'ZOT_REVISION', 1 ); -define ( 'DB_UPDATE_VERSION', 1135 ); +define ( 'DB_UPDATE_VERSION', 1136 ); /** * Constant with a HTML line break. @@ -2194,6 +2194,26 @@ function get_directory_realm() { return DIRECTORY_REALM; } +/** + * @brief Return the primary directory server. + * + * @return string + */ +function get_directory_primary() { + + $dirmode = intval(get_config('system','directory_mode')); + + if($dirmode == DIRECTORY_MODE_STANDALONE || $dirmode == DIRECTORY_MODE_PRIMARY) { + return z_root(); + } + + if($x = get_config('system', 'directory_primary')) + return $x; + + return DIRECTORY_FALLBACK_MASTER; +} + + /** * @brief return relative date of last completed poller execution diff --git a/include/attach.php b/include/attach.php index 155ddbc96..a300e34b0 100644 --- a/include/attach.php +++ b/include/attach.php @@ -970,6 +970,33 @@ function file_activity($channel_id, $object, $allow_cid, $allow_gid, $deny_cid, $poster = get_app()->get_observer(); + //if we got no object something went wrong + if(!$object) + return; + + $is_dir = (($object['flags'] & ATTACH_FLAG_DIR) ? true : false); + + //do not send activity for folders for now + if($is_dir) + return; + +/* + //check for recursive perms if we are in a folder + if($object['folder']) { + + $folder_hash = $object['folder']; + + $r_perms = check_recursive_perms($allow_cid, $allow_gid, $deny_cid, $deny_gid, $folder_hash); + + $allow_cid = perms2str($r_perms['allow_cid']); + $allow_gid = perms2str($r_perms['allow_gid']); + $deny_cid = perms2str($r_perms['deny_cid']); + $deny_gid = perms2str($r_perms['deny_gid']); + + } +*/ + + $mid = item_message_id(); $objtype = ACTIVITY_OBJ_FILE; @@ -1124,3 +1151,83 @@ function get_file_activity_object($channel_id, $hash, $cloudpath) { return $object; } + +function check_recursive_perms($allow_cid, $allow_gid, $deny_cid, $deny_gid, $folder_hash) { + + $arr_allow_cid = expand_acl($allow_cid); + $arr_allow_gid = expand_acl($allow_gid); + $arr_deny_cid = expand_acl($deny_cid); + $arr_deny_gid = expand_acl($deny_gid); + + $count = 0; + while($folder_hash) { + $x = q("SELECT allow_cid, allow_gid, deny_cid, deny_gid, folder FROM attach WHERE hash = '%s' LIMIT 1", + dbesc($folder_hash) + ); + + //only process private folders + if($x[0]['allow_cid'] || $x[0]['allow_gid'] || $x[0]['deny_cid'] || $x[0]['deny_gid']) { + + $parent_arr['allow_cid'][] = expand_acl($x[0]['allow_cid']); + $parent_arr['allow_gid'][] = expand_acl($x[0]['allow_gid']); + $parent_arr['deny_cid'][] = expand_acl($x[0]['deny_cid']); + $parent_arr['deny_gid'][] = expand_acl($x[0]['deny_gid']); + + $parents_arr = $parent_arr; + + $count++; + + } + + $folder_hash = $x[0]['folder']; + + } + + //if there are no perms on the file we get them from the first parent folder + if(!$arr_allow_cid && !$arr_allow_gid && !$arr_deny_cid && !$arr_deny_gid) { + $arr_allow_cid = $parent_arr['allow_cid'][0]; + $arr_allow_gid = $parent_arr['allow_gid'][0]; + $arr_deny_cid = $parent_arr['deny_cid'][0]; + $arr_deny_gid = $parent_arr['deny_gid'][0]; + } + + //allow_cid + foreach ($parents_arr['allow_cid'] as $folder_arr_allow_cid) { + foreach ($folder_arr_allow_cid as $ac_hash) { + $count_values[$ac_hash]++; + } + } + foreach ($arr_allow_cid as $fac_hash) { + if(($count_values[$fac_hash]) && ($count_values[$fac_hash] == $count)) + $r_arr_allow_cid[] = $fac_hash; + } + + + //allow_gid + foreach ($parents_arr['allow_gid'] as $folder_arr_allow_gid) { + foreach ($folder_arr_allow_gid as $ag_hash) { + $count_values[$ag_hash]++; + } + } + foreach ($arr_allow_gid as $fag_hash) { + if(($count_values[$fag_hash]) && ($count_values[$fag_hash] == $count)) + $r_arr_allow_gid[] = $fag_hash; + } + + //deny_gid + foreach($parents_arr['deny_gid'] as $folder_arr_deny_gid) { + $arr_deny_gid = array_merge($arr_deny_gid, $folder_arr_deny_gid); + } + + //deny_cid + foreach($parents_arr['deny_cid'] as $folder_arr_deny_cid) { + $arr_deny_cid = array_merge($arr_deny_cid, $folder_arr_deny_cid); + } + + $ret['allow_gid'] = $r_arr_allow_gid; + $ret['allow_cid'] = $r_arr_allow_cid; + $ret['deny_gid'] = array_unique($r_arr_deny_gid); + $ret['deny_cid'] = array_unique($r_arr_deny_cid); + + return $ret; +} diff --git a/include/conversation.php b/include/conversation.php index 9429af754..952d7d322 100644 --- a/include/conversation.php +++ b/include/conversation.php @@ -619,10 +619,6 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional', $ $profile_link = zid($profile_link); $normalised = normalise_link((strlen($item['author-link'])) ? $item['author-link'] : $item['url']); - if(x($a->contacts,$normalised)) - $profile_avatar = $a->contacts[$normalised]['thumb']; - else - $profile_avatar = ((strlen($item['author-avatar'])) ? $a->get_cached_avatar_image($item['author-avatar']) : $item['thumb']); $profile_name = $item['author']['xchan_name']; $profile_link = $item['author']['xchan_url']; diff --git a/include/diaspora.php b/include/diaspora.php index 8f2f93209..8047d0e64 100755 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -35,11 +35,16 @@ function diaspora_dispatch_public($msg) { logger('diaspora_public: delivering to: ' . $rr['channel_name'] . ' (' . $rr['channel_address'] . ') '); diaspora_dispatch($rr,$msg); } - if($sys) - diaspora_dispatch($sys,$msg); } - else - logger('diaspora_public: no subscribers'); + else { + if(! $sys) + logger('diaspora_public: no subscribers'); + } + + if($sys) { + logger('diaspora_public: delivering to sys.'); + diaspora_dispatch($sys,$msg); + } } @@ -688,7 +693,7 @@ function diaspora_request($importer,$xml) { $default_perms = $x['perms_accept']; } if(! $default_perms) - $default_perms = intval(get_pconfig($channel['channel_id'],'system','autoperms')); + $default_perms = intval(get_pconfig($importer['channel_id'],'system','autoperms')); $their_perms = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_PHOTOS|PERMS_R_ABOOK|PERMS_W_STREAM|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT|PERMS_R_STORAGE|PERMS_R_PAGES; diff --git a/include/dir_fns.php b/include/dir_fns.php index 6d06fddd1..8c0161ff1 100644 --- a/include/dir_fns.php +++ b/include/dir_fns.php @@ -216,6 +216,49 @@ function sync_directories($dirmode) { ); } } + if(count($j['ratings'])) { + foreach($j['ratings'] as $rr) { + $x = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1", + dbesc($rr['channel']), + dbesc($rr['target']) + ); + if($x && $x[0]['xlink_updated'] >= $rr['edited']) + continue; + $y = q("select xchan_pubkey from xchan where xchan_hash = '%s' limit 1", + dbesc($rr['channel']) + ); + if(! $y) { + logger('key unavailable on this site for ' . $rr['channel']); + continue; + } + if(! rsa_verify($rr['target'] . '.' . $rr['rating'] . '.' . $rr['rating_text'], base64url_decode($rr['signature']),$y[0]['xchan_pubkey'])) { + logger('failed to verify rating'); + continue; + } + + if($x) { + $z = q("update xlink set xlink_rating = %d, xlink_rating_text = '%s', xlink_sig = '%s', xlink_updated = '%s' where xlink_id = %d", + intval($rr['rating']), + dbesc($rr['rating_text']), + dbesc($rr['signature']), + dbesc(datetime_convert()), + intval($x[0]['xlink_id']) + ); + logger('rating updated'); + } + else { + $z = q("insert into xlink ( xlink_xchan, xlink_link, xlink_rating, xlink_rating_text, xlink_sig, xlink_updated, xlink_static ) values( '%s', '%s', %d, '%s', '%s', 1 ) ", + dbesc($rr['channel']), + dbesc($rr['target']), + intval($rr['rating']), + dbesc($rr['rating_text']), + dbesc($rr['signature']), + dbesc(datetime_convert()) + ); + logger('rating created'); + } + } + } } } diff --git a/include/identity.php b/include/identity.php index 2fc183368..415e85f2f 100644 --- a/include/identity.php +++ b/include/identity.php @@ -943,6 +943,9 @@ logger('online: ' . $profile['online']); $tpl = get_markup_template('profile_vcard.tpl'); + require_once('include/widgets.php'); + $z = widget_rating(array('target' => $profile['channel_hash'])); + $o .= replace_macros($tpl, array( '$profile' => $profile, '$connect' => $connect, @@ -954,6 +957,7 @@ logger('online: ' . $profile['online']); '$homepage' => $homepage, '$chanmenu' => $channel_menu, '$diaspora' => $diaspora, + '$rating' => $z, '$contact_block' => $contact_block, )); diff --git a/include/items.php b/include/items.php index c34e694ac..0b2106572 100755 --- a/include/items.php +++ b/include/items.php @@ -4751,6 +4751,7 @@ function item_remove_cid($xchan_hash,$mid,$uid) { // Set item permissions based on results obtained from linkify_tags() function set_linkified_perms($linkified, &$str_contact_allow, &$str_group_allow, $profile_uid, $parent_item = false) { $first_access_tag = true; + foreach($linkified as $x) { $access_tag = $x['access_tag']; if(($access_tag) && (! $parent_item)) { diff --git a/include/js_strings.php b/include/js_strings.php index f4c0a631d..56ffa9536 100644 --- a/include/js_strings.php +++ b/include/js_strings.php @@ -16,6 +16,10 @@ function js_strings() { '$permschange' => t('Notice: Permissions have changed but have not yet been submitted.'), '$closeAll' => t('close all'), '$nothingnew' => t('Nothing new here'), + '$rating_desc' => t('Rate This Channel (this is public)'), + '$rating_val' => t('Rating'), + '$rating_text' => t('Describe (optional)'), + '$submit' => t('Submit'), '$t01' => ((t('timeago.prefixAgo') != 'timeago.prefixAgo') ? t('timeago.prefixAgo') : ''), '$t02' => ((t('timeago.prefixFromNow') != 'timeago.prefixFromNow') ? t('timeago.prefixFromNow') : ''), diff --git a/include/notifier.php b/include/notifier.php index edb2f1946..fe6ac33c0 100644 --- a/include/notifier.php +++ b/include/notifier.php @@ -297,15 +297,6 @@ function notifier_run($argv, $argc){ $private = false; $packet_type = 'purge'; } - elseif($cmd === 'rating') { - $r = q("select * from xlink where xlink_id = %d and xlink_static = 1 limit 1", - intval($item_id) - ); - if($r) { - logger('rating message: ' . print_r($r[0],true)); - return; - } - } else { // Normal items @@ -483,11 +474,6 @@ function notifier_run($argv, $argc){ // Now we have collected recipients (except for external mentions, FIXME) // Let's reduce this to a set of hubs. - - // for public posts always include our own hub -// this shouldn't be needed any more. collect_recipients should take care of it. -// $sql_extra = (($private) ? "" : " or hubloc_url = '" . dbesc(z_root()) . "' "); - logger('notifier: hub choice: ' . intval($relay_to_owner) . ' ' . intval($private) . ' ' . $cmd, LOGGER_DEBUG); if($relay_to_owner && (! $private) && ($cmd !== 'relay')) { diff --git a/include/poller.php b/include/poller.php index dc310fe16..fd78ce087 100644 --- a/include/poller.php +++ b/include/poller.php @@ -164,6 +164,10 @@ function poller_run($argv, $argc){ db_utcnow(), db_quoteinterval('14 DAY') ); + $dirmode = intval(get_config('system','directory_mode')); + if($dirmode == DIRECTORY_MODE_SECONDARY) { + logger('regdir: ' . print_r(z_fetch_url(get_directory_primary() . '/regdir?f=&url=' . z_root() . '&realm=' . get_directory_realm()),true)); + } /** * End Cron Weekly diff --git a/include/ratenotif.php b/include/ratenotif.php new file mode 100644 index 000000000..4fa0077a6 --- /dev/null +++ b/include/ratenotif.php @@ -0,0 +1,124 @@ + 'rating', + 'encoding' => 'zot', + 'target' => $r[0]['xlink_link'], + 'rating' => intval($r[0]['xlink_rating']), + 'rating_text' => $r[0]['xlink_rating_text'], + 'signature' => $r[0]['xlink_sig'], + 'edited' => $r[0]['xlink_updated'] + ); + } + + $channel = channelx_by_hash($r[0]['xlink_xchan']); + if(! $channel) { + logger('no channel'); + return; + } + + + $primary = get_directory_primary(); + + if(! $primary) + return; + + + $interval = ((get_config('system','delivery_interval') !== false) + ? intval(get_config('system','delivery_interval')) : 2 ); + + $deliveries_per_process = intval(get_config('system','delivery_batch_count')); + + if($deliveries_per_process <= 0) + $deliveries_per_process = 1; + + $deliver = array(); + + $x = z_fetch_url($primary . '/regdir'); + if($x['success']) { + $j = json_decode($x['body'],true); + if($j && $j['success'] && is_array($j['directories'])) { + + foreach($j['directories'] as $h) { +// if($h == z_root()) +// continue; + + $hash = random_string(); + $n = zot_build_packet($channel,'notify',null,null,$hash); + + q("insert into outq ( outq_hash, outq_account, outq_channel, outq_driver, outq_posturl, outq_async, outq_created, outq_updated, outq_notify, outq_msg ) values ( '%s', %d, %d, '%s', '%s', %d, '%s', '%s', '%s', '%s' )", + dbesc($hash), + intval($channel['channel_account_id']), + intval($channel['channel_id']), + dbesc('zot'), + dbesc($h . '/post'), + intval(1), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc($n), + dbesc(json_encode($encoded_item)) + ); + } + $deliver[] = $hash; + + if(count($deliver) >= $deliveries_per_process) { + proc_run('php','include/deliver.php',$deliver); + $deliver = array(); + if($interval) + @time_sleep_until(microtime(true) + (float) $interval); + } + + + // catch any stragglers + + if(count($deliver)) { + proc_run('php','include/deliver.php',$deliver); + } + } + } + + logger('ratenotif: complete.'); + return; + +} + +if (array_search(__file__,get_included_files())===0){ + ratenotif_run($argv,$argc); + killme(); +} diff --git a/include/text.php b/include/text.php index 5c8242e79..ef2fc2371 100644 --- a/include/text.php +++ b/include/text.php @@ -1946,9 +1946,9 @@ function find_xchan_in_array($xchan,$arr) { } function get_rel_link($j,$rel) { - if(count($j)) + if(is_array($j) && ($j)) foreach($j as $l) - if($l['rel'] === $rel) + if(array_key_exists('rel',$j) && $l['rel'] === $rel && array_key_exists('href',$l)) return $l['href']; return ''; @@ -2297,6 +2297,7 @@ function handle_tag($a, &$body, &$access_tag, &$str_tags, $profile_uid, $tag) { } } else { + // check for a group/collection exclusion tag // note that we aren't setting $replaced even though we're replacing text. @@ -2357,6 +2358,8 @@ function linkify_tags($a, &$body, $uid) { $tags = get_tags($body); if(count($tags)) { foreach($tags as $tag) { + $access_tag = ''; + // If we already tagged 'Robert Johnson', don't try and tag 'Robert'. // Robert Johnson should be first in the $tags array @@ -2388,6 +2391,7 @@ function getIconFromType($type) { $iconMap = array( //Folder t('Collection') => 'icon-folder-close', + 'multipart/mixed' => 'icon-folder-close', //dirs in attach use this mime type //Common file 'application/octet-stream' => 'icon-file-alt', //Text diff --git a/include/widgets.php b/include/widgets.php index 523318850..d457db07d 100644 --- a/include/widgets.php +++ b/include/widgets.php @@ -903,3 +903,63 @@ function widget_random_block($arr) { return $o; } + + +function widget_rating($arr) { + $a = get_app(); + + $poco_rating = get_config('system','poco_rating_enable'); + if((! $poco_rating) && ($poco_rating !== false)) { + return; + } + + if($arr['target']) + $hash = $arr['target']; + else + $hash = $a->poi['xchan_hash']; + + if(! $hash) + return; + + $url = ''; + $remote = false; + + if(remote_channel() && ! local_channel()) { + $ob = $a->get_observer(); + if($ob && $ob['xchan_url']) { + $p = parse_url($ob['xchan_url']); + if($p) { + $url = $p['scheme'] . '://' . $p['host'] . (($p['port']) ? ':' . $p['port'] : ''); + $url .= '/rate?f=&target=' . urlencode($hash); + } + $remote = true; + } + } + + $self = false; + + if(local_channel()) { + $channel = $a->get_channel(); + + if($hash == $channel['channel_hash']) + $self = true; + + head_add_js('ratings.js'); + + } + + if((($remote) || (local_channel())) && (! $self)) { + $o = '
'; + } + + $o .= ' '; + + return $o; + +} \ No newline at end of file diff --git a/include/zot.php b/include/zot.php index 5faabd5ec..7e9a6ee54 100644 --- a/include/zot.php +++ b/include/zot.php @@ -1084,6 +1084,11 @@ function zot_import($arr, $sender_url) { if(is_array($incoming)) { foreach($incoming as $i) { + if(! is_array($i)) { + logger('incoming is not an array'); + continue; + } + $result = null; if(array_key_exists('iv',$i['notify'])) { @@ -1108,7 +1113,8 @@ function zot_import($arr, $sender_url) { if(array_key_exists('message',$i) && array_key_exists('type',$i['message']) && $i['message']['type'] === 'rating') { // rating messages are processed only by directory servers logger('Rating received: ' . print_r($arr,true), LOGGER_DATA); - $result = process_rating_delivery($i['notify']['sender'],$arr); + $result = process_rating_delivery($i['notify']['sender'],$i['message']); + continue; } if(array_key_exists('recipients',$i['notify']) && count($i['notify']['recipients'])) { @@ -1813,34 +1819,52 @@ function process_mail_delivery($sender,$arr,$deliveries) { function process_rating_delivery($sender,$arr) { - $dirmode = intval(get_config('system','directory_mode')); - if($dirmode == DIRECTORY_MODE_NORMAL) - return; + logger('process_rating_delivery: ' . print_r($arr,true)); if(! $arr['target']) return; - $r = q("select * from xlink where xlink_xchan = '%s' and xlink_target = '%s' limit 1", + $z = q("select xchan_pubkey from xchan where xchan_hash = '%s' limit 1", + dbesc($sender['hash']) + ); + + + if((! $z) || (! rsa_verify($arr['target'] . '.' . $arr['rating'] . '.' . $arr['rating_text'], base64url_decode($arr['signature']),$z[0]['xchan_pubkey']))) { + logger('failed to verify rating'); + return; + } + + $r = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1 limit 1", dbesc($sender['hash']), dbesc($arr['target']) - ); + ); + if($r) { - $x = q("update xlink set xlink_rating = %d, xlink_rating_text = '%s', xlink_updated = '%s' where xlink_id = %d", + if($r[0]['xlink_updated'] >= $arr['edited']) { + logger('rating message duplicate'); + return; + } + + $x = q("update xlink set xlink_rating = %d, xlink_rating_text = '%s', xlink_sig = '%s', xlink_updated = '%s' where xlink_id = %d", intval($arr['rating']), - intval($arr['rating_text']), + dbesc($arr['rating_text']), + dbesc($arr['signature']), dbesc(datetime_convert()), intval($r[0]['xlink_id']) ); + logger('rating updated'); } else { - $x = q("insert into xlink ( xlink_xchan, xlink_link, xlink_rating, xlink_rating_text, xlink_updated, xlink_static ) + $x = q("insert into xlink ( xlink_xchan, xlink_link, xlink_rating, xlink_rating_text, xlink_sig, xlink_updated, xlink_static ) values( '%s', '%s', %d, '%s', '%s', 1 ) ", dbesc($sender['hash']), dbesc($arr['target']), intval($arr['rating']), - intval($arr['rating_text']), + dbesc($arr['rating_text']), + dbesc($arr['signature']), dbesc(datetime_convert()) ); + logger('rating created'); } return; } diff --git a/install/schema_mysql.sql b/install/schema_mysql.sql index ba41073f8..8addc0af9 100644 --- a/install/schema_mysql.sql +++ b/install/schema_mysql.sql @@ -1533,6 +1533,7 @@ CREATE TABLE IF NOT EXISTS `xlink` ( `xlink_rating_text` TEXT NOT NULL DEFAULT '', `xlink_updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `xlink_static` tinyint(1) NOT NULL DEFAULT '0', + `xlink_sig` text NOT NULL DEFAULT '', PRIMARY KEY (`xlink_id`), KEY `xlink_xchan` (`xlink_xchan`), KEY `xlink_link` (`xlink_link`), diff --git a/install/schema_postgres.sql b/install/schema_postgres.sql index 2fcc7f9ba..b68a7cb97 100644 --- a/install/schema_postgres.sql +++ b/install/schema_postgres.sql @@ -1146,6 +1146,7 @@ CREATE TABLE "xlink" ( "xlink_rating_text" TEXT NOT NULL DEFAULT '', "xlink_updated" timestamp NOT NULL DEFAULT '0001-01-01 00:00:00', "xlink_static" numeric(1) NOT NULL DEFAULT '0', + "xlink_sig" text NOT NULL DEFAULT '', PRIMARY KEY ("xlink_id") ); create index "xlink_xchan" on xlink ("xlink_xchan"); diff --git a/install/update.php b/install/update.php index 2ea64bc37..8ce50926a 100644 --- a/install/update.php +++ b/install/update.php @@ -1,6 +1,6 @@ 10) $rating = 10; - $rating_text = escape_tags($_REQUEST['rating_text']); + $rating_text = trim(escape_tags($_REQUEST['rating_text'])); $abook_my_perms = 0; @@ -131,26 +131,35 @@ function connedit_post(&$a) { $new_friend = false; if(! $is_self) { - $z = q("select * from xlink where xlink_xchan = '%s' and xlink_xlink = '%s' and xlink_static = 1 limit 1", + + $signed = $orig_record[0]['abook_xchan'] . '.' . $rating . '.' . $rating_text; + + $sig = base64url_encode(rsa_sign($signed,$channel['channel_prvkey'])); + + $z = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1 limit 1", dbesc($channel['channel_hash']), dbesc($orig_record[0]['abook_xchan']) ); + + if($z) { $record = $z[0]['xlink_id']; - $w = q("update xlink set xlink_rating = '%d', xlink_rating_text = '%s', xlink_updated = '%s' + $w = q("update xlink set xlink_rating = '%d', xlink_rating_text = '%s', xlink_sig = '%s', xlink_updated = '%s' where xlink_id = %d", intval($rating), dbesc($rating_text), + dbesc($sig), dbesc(datetime_convert()), intval($record) ); } else { - $w = q("insert into xlink ( xlink_xchan, xlink_link, xlink_rating, xlink_rating_text, xlink_updated, xlink_static ) values ( '%s', '%s', %d, '%s', '%s', 1 ) ", + $w = q("insert into xlink ( xlink_xchan, xlink_link, xlink_rating, xlink_rating_text, xlink_sig, xlink_updated, xlink_static ) values ( '%s', '%s', %d, '%s', '%s', '%s', 1 ) ", dbesc($channel['channel_hash']), dbesc($orig_record[0]['abook_xchan']), intval($rating), dbesc($rating_text), + dbesc($sig), dbesc(datetime_convert()) ); $z = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1 limit 1", @@ -161,7 +170,7 @@ function connedit_post(&$a) { $record = $z[0]['xlink_id']; } if($record) { - proc_run('php','include/notifier.php','rating',$record); + proc_run('php','include/ratenotif.php','rating',$record); } } @@ -171,13 +180,11 @@ function connedit_post(&$a) { } - $r = q("UPDATE abook SET abook_profile = '%s', abook_my_perms = %d , abook_closeness = %d, abook_rating = %d, abook_rating_text = '%s', abook_flags = %d + $r = q("UPDATE abook SET abook_profile = '%s', abook_my_perms = %d , abook_closeness = %d, abook_flags = %d where abook_id = %d AND abook_channel = %d", dbesc($profile_id), intval($abook_my_perms), intval($closeness), - intval($rating), - dbesc($rating_text), intval($abook_flags), intval($contact_id), intval(local_channel()) @@ -315,6 +322,7 @@ function connedit_content(&$a) { return login(); } + $channel = $a->get_channel(); $my_perms = get_channel_default_perms(local_channel()); $role = get_pconfig(local_channel(),'system','permissions_role'); if($role) { @@ -563,8 +571,22 @@ function connedit_content(&$a) { )); } + $rating_val = 0; + $rating_text = ''; + + $xl = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1", + dbesc($channel['channel_hash']), + dbesc($contact['xchan_hash']) + ); + + if($xl) { + $rating_val = intval($xl[0]['xlink_rating']); + $rating_text = $xl[0]['xlink_rating_text']; + } + + $poco_rating = get_config('system','poco_rating_enable'); - $poco_rating = 0; + // if unset default to enabled if($poco_rating === false) $poco_rating = true; @@ -572,7 +594,7 @@ function connedit_content(&$a) { if($poco_rating) { $rating = replace_macros(get_markup_template('rating_slider.tpl'),array( '$min' => -10, - '$val' => (($contact['abook_rating']) ? $contact['abook_rating'] : 0), + '$val' => $rating_val )); } else { @@ -612,11 +634,11 @@ function connedit_content(&$a) { '$viewprof' => t('View Profile'), '$clickme' => t('Click to open/close'), '$lbl_slider' => t('Slide to adjust your degree of friendship'), - '$lbl_rating' => t('Rating (this information may be public)'), - '$lbl_rating_txt' => t('Optionally explain your rating (this information may be public)'), - '$rating_txt' => $contact['abook_rating_text'], + '$lbl_rating' => t('Rating (this information is public)'), + '$lbl_rating_txt' => t('Optionally explain your rating (this information is public)'), + '$rating_txt' => $rating_text, '$rating' => $rating, - '$rating_val' => $contact['abook_rating'], + '$rating_val' => $rating_val, '$slide' => $slide, '$tabs' => $t, '$tab_str' => $tab_str, diff --git a/mod/directory.php b/mod/directory.php index 21940d57b..329e255cf 100644 --- a/mod/directory.php +++ b/mod/directory.php @@ -190,6 +190,11 @@ function directory_content(&$a) { $page_type = ''; + if($rr['total_ratings']) + $total_ratings = sprintf( tt("%d rating", "%d ratings", $rr['total_ratings']), $rr['total_ratings']); + else + $total_ratings = ''; + $profile = $rr; if ((x($profile,'locale') == 1) @@ -247,7 +252,7 @@ function directory_content(&$a) { 'public_forum' => $rr['public_forum'], 'photo' => $rr['photo'], 'hash' => $rr['hash'], - 'alttext' => $rr['name'] . ' ' . $rr['address'], + 'alttext' => $rr['name'] . ((local_channel() || remote_channel()) ? ' ' . $rr['address'] : ''), 'name' => $rr['name'], 'details' => $pdesc . $details, 'profile' => $profile, @@ -255,6 +260,9 @@ function directory_content(&$a) { 'nickname' => substr($rr['address'],0,strpos($rr['address'],'@')), 'location' => $location, 'gender' => $gender, + 'total_ratings' => $total_ratings, + 'viewrate' => true, + 'canrate' => ((local_channel()) ? true : false), 'pdesc' => $pdesc, 'marital' => $marital, 'homepage' => $homepage, @@ -269,6 +277,7 @@ function directory_content(&$a) { 'keywords' => $out, 'ignlink' => $suggest ? $a->get_baseurl() . '/directory?ignore=' . $rr['hash'] : '', 'ignore_label' => "Don't suggest", + 'safe' => $safe_mode ); $arr = array('contact' => $rr, 'entry' => $entry); diff --git a/mod/dirsearch.php b/mod/dirsearch.php index 52a3d02cf..5a0a7cee8 100644 --- a/mod/dirsearch.php +++ b/mod/dirsearch.php @@ -12,7 +12,6 @@ function dirsearch_content(&$a) { $ret = array('success' => false); - // If you've got a public directory server, you probably shouldn't block public access $dirmode = intval(get_config('system','directory_mode')); @@ -210,6 +209,24 @@ function dirsearch_content(&$a) { ); } } + $r = q("select * from xlink where xlink_static = 1 and xlink_updated >= '%s' ", + dbesc($sync) + ); + if($r) { + $spkt['ratings'] = array(); + foreach($r as $rr) { + $spkt['ratings'][] = array( + 'type' => 'rating', + 'encoding' => 'zot', + 'channel' => $rr['xlink_xchan'], + 'target' => $rr['xlink_link'], + 'rating' => intval($rr['xlink_rating']), + 'rating_text' => $rr['xlink_rating_text'], + 'signature' => $rr['xlink_sig'], + 'edited' => $rr['xlink_updated'] + ); + } + } json_return_and_die($spkt); } else { diff --git a/mod/prate.php b/mod/prate.php index 28703d414..b89d16f42 100644 --- a/mod/prate.php +++ b/mod/prate.php @@ -1,13 +1,35 @@ get_channel(); - $target = $_REQUEST['target']; + $target = argv(1); + if(! $target) + return; + + $r = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1", + dbesc($channel['channel_hash']), + dbesc($target) + ); + if($r) + json_return_and_die(array('rating' => $r[0]['xlink_rating'],'rating_text' => $r[0]['xlink_rating_text'])); + killme(); +} + +function prate_post(&$a) { + + if(! local_channel()) + return; + + $channel = $a->get_channel(); + + $target = trim($_REQUEST['target']); if(! $target) return; @@ -20,28 +42,35 @@ function prate_post(&$a) { if($rating > 10) $rating = 10; - $rating_text = escape_tags($_REQUEST['rating_text']); + $rating_text = trim(escape_tags($_REQUEST['rating_text'])); - $z = q("select * from xlink where xlink_xchan = '%s' and xlink_xlink = '%s' and xlink_static = 1 limit 1", + $signed = $target . '.' . $rating . '.' . $rating_text; + + $sig = base64url_encode(rsa_sign($signed,$channel['channel_prvkey'])); + + + $z = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1 limit 1", dbesc($channel['channel_hash']), dbesc($target) ); if($z) { $record = $z[0]['xlink_id']; - $w = q("update xlink set xlink_rating = '%d', xlink_rating_text = '%s', xlink_updated = '%s' + $w = q("update xlink set xlink_rating = '%d', xlink_rating_text = '%s', xlink_sig = '%s', xlink_updated = '%s' where xlink_id = %d", intval($rating), dbesc($rating_text), + dbesc($sig), dbesc(datetime_convert()), intval($record) ); } else { - $w = q("insert into xlink ( xlink_xchan, xlink_link, xlink_rating, xlink_rating_text, xlink_updated, xlink_static ) values ( '%s', '%s', %d, '%s', '%s', 1 ) ", + $w = q("insert into xlink ( xlink_xchan, xlink_link, xlink_rating, xlink_rating_text, xlink_sig, xlink_updated, xlink_static ) values ( '%s', '%s', %d, '%s', '%s', '%s', 1 ) ", dbesc($channel['channel_hash']), dbesc($target), intval($rating), dbesc($rating_text), + dbesc($sig), dbesc(datetime_convert()) ); $z = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1 limit 1", @@ -52,32 +81,10 @@ function prate_post(&$a) { $record = $z[0]['xlink_id']; } if($record) { - proc_run('php','include/notifier.php','rating',$record); + proc_run('php','include/ratenotif.php','rating',$record); } - $x = q("select abook_id from abook where abook_xchan = '%s' and abook_channel = %d limit 1", - dbesc($target), - intval($local_channel()) - ); - if($x) { - $w = q("update abook set abook_rating = %d, abook_rating_text = '%s' where abook_xchan = '%s' and abook_channel = %d", - intval($rating), - dbesc($rating_text), - dbesc($target), - intval(local_channel()) - ); - $x = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1", - dbesc($target), - intval($local_channel()) - ); - if($x) { - unset($x[0]['abook_id']); - unset($x[0]['abook_account']); - unset($x[0]['abook_channel']); - build_sync_packet(0, array('abook' => array($x[0]))); - } - } - return; + json_return_and_die(array('result' => true));; } @@ -89,3 +96,4 @@ function prate_post(&$a) { + diff --git a/mod/prep.php b/mod/prep.php deleted file mode 100644 index 896717826..000000000 --- a/mod/prep.php +++ /dev/null @@ -1,75 +0,0 @@ - 1) - $hash = argv(1); - - if(! $hash) { - notice('Must supply a channel identififier.'); - return; - } - - if(strpos($hash,'@')) { - $r = q("select * from hubloc where hubloc_addr = '%s' limit 1", - dbesc($hash) - ); - if($r) - $hash = $r[0]['hubloc_hash']; - } - - $p = q("select * from xchan where xchan_hash like '%s'", - dbesc($hash . '%') - ); - - if($p) - $a->poi = $p[0]; - -} - - - - - -function prep_content(&$a) { - - - $poco_rating = get_config('system','poco_rating_enable'); - // if unset default to enabled - if($poco_rating === false) - $poco_rating = true; - - if(! $poco_rating) - return; - - if(! $a->poi) - return; - - $r = q("select * from xlink left join xchan on xlink_xchan = xchan_hash where xlink_link like '%s' and xlink_rating != 0", - dbesc($a->poi['xchan_hash']) - ); - - if(! $r) - notice( t('No ratings available') . EOL); - - - $o = replace_macros(get_markup_template('prep.tpl'),array( - '$header' => t('Ratings'), - '$rating_lbl' => t('Rating: ' ), - '$rating_text_lbl' => t('Description: '), - '$raters' => $r - )); - - return $o; -} - - \ No newline at end of file diff --git a/mod/rate.php b/mod/rate.php new file mode 100644 index 000000000..694b88ddd --- /dev/null +++ b/mod/rate.php @@ -0,0 +1,158 @@ +get_channel(); + + $target = $_REQUEST['target']; + if(! $target) + return; + + $a->data['target'] = $target; + + if($target) { + $r = q("SELECT * FROM xchan where xchan_hash like '%s' LIMIT 1", + dbesc($target) + ); + if($r) { + $a->poi = $r[0]; + } + } + + + return; + +} + + +function rate_post(&$a) { + + if(! local_channel()) + return; + + if(! $a->data['target']) + return; + + if(! $_REQUEST['execute']) + return; + + $channel = $a->get_channel(); + + $rating = intval($_POST['rating']); + if($rating < (-10)) + $rating = (-10); + if($rating > 10) + $rating = 10; + + $rating_text = trim(escape_tags($_REQUEST['rating_text'])); + + $signed = $a->data['target'] . '.' . $rating . '.' . $rating_text; + + $sig = base64url_encode(rsa_sign($signed,$channel['channel_prvkey'])); + + $z = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1 limit 1", + dbesc($channel['channel_hash']), + dbesc($a->data['target']) + ); + + if($z) { + $record = $z[0]['xlink_id']; + $w = q("update xlink set xlink_rating = '%d', xlink_rating_text = '%s', xlink_sig = '%s', xlink_updated = '%s' + where xlink_id = %d", + intval($rating), + dbesc($rating_text), + dbesc($sig), + dbesc(datetime_convert()), + intval($record) + ); + } + else { + $w = q("insert into xlink ( xlink_xchan, xlink_link, xlink_rating, xlink_rating_text, xlink_sig, xlink_updated, xlink_static ) values ( '%s', '%s', %d, '%s', '%s', '%s', 1 ) ", + dbesc($channel['channel_hash']), + dbesc($a->data['target']), + intval($rating), + dbesc($rating_text), + dbesc($sig), + dbesc(datetime_convert()) + ); + $z = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1 limit 1", + dbesc($channel['channel_hash']), + dbesc($a->data['target']) + ); + if($z) + $record = $z[0]['xlink_id']; + } + + if($record) { + proc_run('php','include/ratenotif.php','rating',$record); + } + +} + + + +function rate_content(&$a) { + + if(! local_channel()) { + notice( t('Permission denied.') . EOL); + return; + } + +// if(! $a->data['target']) { +// notice( t('No recipients.') . EOL); +// return; +// } + + $poco_rating = get_config('system','poco_rating_enable'); + if((! $poco_rating) && ($poco_rating !== false)) { + notice('Ratings are disabled on this site.'); + return; + } + + $channel = $a->get_channel(); + + $r = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1", + dbesc($channel['channel_hash']), + dbesc($a->data['target']) + ); + if($r) + $a->data['xlink'] = $r[0]; + + $rating_val = $r[0]['xlink_rating']; + $rating_text = $r[0]['xlink_rating_text']; + + + // if unset default to enabled + if($poco_rating === false) + $poco_rating = true; + + if($poco_rating) { + $rating = replace_macros(get_markup_template('rating_slider.tpl'),array( + '$min' => -10, + '$val' => $rating_val + )); + } + else { + $rating = false; + } + + $o = replace_macros(get_markup_template('rating_form.tpl'),array( + '$header' => t('Rating'), + 'target' => $a->data['target'], + '$tgt_name' => (($a->poi && $a->poi['xchan_name']) ? $a->poi['xchan_name'] : sprintf( t('Remote Channel [%s] (not yet known on this site)'), substr($a->data['target'],0,16))), + '$lbl_rating' => t('Rating (this information is public)'), + '$lbl_rating_txt' => t('Optionally explain your rating (this information is public)'), + '$rating_txt' => $rating_text, + '$rating' => $rating, + '$rating_val' => $rating_val, + '$slide' => $slide, + '$submit' => t('Submit') + )); + + return $o; + +} \ No newline at end of file diff --git a/mod/ratings.php b/mod/ratings.php new file mode 100644 index 000000000..fe7865778 --- /dev/null +++ b/mod/ratings.php @@ -0,0 +1,103 @@ + 1) + $hash = argv(1); + + if(! $hash) { + notice('Must supply a channel identififier.'); + return; + } + + $results = false; + + $x = z_fetch_url($url . '/ratingsearch/' . $hash); + + + if($x['success']) + $results = json_decode($x['body'],true); + + + if((! $results) || (! $results['success'])) { + + notice('No results.'); + return; + } + + $a->poi = $results['target']; + + $friends = array(); + $others = array(); + + if($results['ratings']) { + foreach($results['ratings'] as $n) { + if(is_array($a->contacts) && array_key_exists($n['xchan_hash'],$a->contacts)) + $friends[] = $n; + else + $others[] = $n; + } + } + + $a->data = array_merge($friends,$others); + + if(! $a->data) { + notice( t('No ratings') . EOL); + } + + return; +} + + + + + +function ratings_content(&$a) { + + if((get_config('system','block_public')) && (! local_channel()) && (! remote_channel())) { + notice( t('Public access denied.') . EOL); + return; + } + + $poco_rating = get_config('system','poco_rating_enable'); + // if unset default to enabled + if($poco_rating === false) + $poco_rating = true; + + if(! $poco_rating) + return; + + $o = replace_macros(get_markup_template('prep.tpl'),array( + '$header' => t('Ratings'), + '$rating_lbl' => t('Rating: ' ), + '$rating_text_lbl' => t('Description: '), + '$raters' => $a->data + )); + + return $o; +} + + \ No newline at end of file diff --git a/mod/ratingsearch.php b/mod/ratingsearch.php new file mode 100644 index 000000000..ec2db570b --- /dev/null +++ b/mod/ratingsearch.php @@ -0,0 +1,58 @@ + false); + + $dirmode = intval(get_config('system','directory_mode')); + + if($dirmode == DIRECTORY_MODE_NORMAL) { + $ret['message'] = 'This site is not a directory server.'; + json_return_and_die($ret); + } + + if(argc() > 1) + $hash = argv(1); + + if(! $hash) { + $ret['message'] = 'No channel identifier'; + json_return_and_die($ret); + } + + if(strpos($hash,'@')) { + $r = q("select * from hubloc where hubloc_addr = '%s' limit 1", + dbesc($hash) + ); + if($r) + $hash = $r[0]['hubloc_hash']; + } + + $p = q("select * from xchan where xchan_hash like '%s'", + dbesc($hash . '%') + ); + + if($p) + $ret['target'] = $p[0]; + else { + $ret['message'] = 'channel not found'; + json_return_and_die($ret); + } + + $ret['success'] = true; + + $r = q("select * from xlink left join xchan on xlink_xchan = xchan_hash + where xlink_link = '%s' and xlink_rating != 0 and xlink_static = 1 order by xchan_name asc", + dbesc($p[0]['xchan_hash']) + ); + + if($r) { + $ret['ratings'] = $r; + } + else + $ret['ratings'] = array(); + + json_return_and_die($ret); + +} + diff --git a/mod/regdir.php b/mod/regdir.php new file mode 100644 index 000000000..eecc99ca5 --- /dev/null +++ b/mod/regdir.php @@ -0,0 +1,69 @@ + false); + + $url = $_REQUEST['url']; + + + // we probably don't need the realm as we will find out in the probe. + // What we may want to die is throw an error if you're trying to register in a different realm + // so this configuration issue can be discovered. + + $realm = $_REQUEST['realm']; + if(! $realm) + $realm = DIRECTORY_REALM; + + $dirmode = intval(get_config('system','directory_mode')); + + if($dirmode == DIRECTORY_MODE_NORMAL) { + $ret['message'] = t('This site is not a directory server'); + json_return_and_die($ret); + } + + $m = null; + if($url) { + $m = parse_url($url); + + if((! $m) || (! @dns_get_record($m['host'], DNS_A + DNS_CNAME + DNS_PTR)) || (! filter_var($m['host'], FILTER_VALIDATE_IP) )) { + $result['message'] = 'unparseable url'; + json_return_and_die($result); + } + + $f = zot_finger('sys@' . $m['host']); + if($f['success']) { + $j = json_decode($f['body'],true); + if($j['success'] && $j['guid']) { + $x = import_xchan($j); + if($x['success']) { + $result['success'] = true; + json_return_and_die($result); + } + } + } + + json_return_and_die($result); + } + else { + if($dirmode == DIRECTORY_MODE_STANDALONE) { + $r = array(array('site_url' => z_root())); + } + else { + $r = q("select site_url from site where site_flags in ( 1, 2 ) and site_realm = '%s'", + dbesc(get_directory_realm()) + ); + } + if($r) { + $result['success'] = true; + $result['directories'] = array(); + foreach($r as $rr) + $result['directories'][] = $rr['site_url']; + json_return_and_die($result); + } + } + json_return_and_die($result); + + +} \ No newline at end of file diff --git a/mod/zfinger.php b/mod/zfinger.php index 6f4febc6f..f4b7efd96 100644 --- a/mod/zfinger.php +++ b/mod/zfinger.php @@ -99,13 +99,14 @@ function zfinger_init(&$a) { $id = $e['channel_id']; + $sys_channel = (($e['channel_pageflags'] & PAGE_SYSTEM) ? true : false); $special_channel = (($e['channel_pageflags'] & PAGE_PREMIUM) ? true : false); $adult_channel = (($e['channel_pageflags'] & PAGE_ADULT) ? true : false); $censored = (($e['channel_pageflags'] & PAGE_CENSORED) ? true : false); $searchable = (($e['channel_pageflags'] & PAGE_HIDDEN) ? false : true); $deleted = (($e['xchan_flags'] & XCHAN_FLAGS_DELETED) ? true : false); - if($deleted || $censored) + if($deleted || $censored || $sys_channel) $searchable = false; $public_forum = false; @@ -237,6 +238,12 @@ function zfinger_init(&$a) { $dirmode = get_config('system','directory_mode'); if(($dirmode === false) || ($dirmode == DIRECTORY_MODE_NORMAL)) $ret['site']['directory_mode'] = 'normal'; + + // downgrade mis-configured primaries + + if($dirmode == DIRECTORY_MODE_PRIMARY && z_root() != get_directory_primary()) + $dirmode = DIRECTORY_MODE_SECONDARY; + if($dirmode == DIRECTORY_MODE_PRIMARY) $ret['site']['directory_mode'] = 'primary'; elseif($dirmode == DIRECTORY_MODE_SECONDARY) diff --git a/version.inc b/version.inc index ff47debf0..8d8816a23 100644 --- a/version.inc +++ b/version.inc @@ -1 +1 @@ -2015-01-30.932 +2015-02-04.935 diff --git a/view/css/mod_directory.css b/view/css/mod_directory.css index 20facbaf8..7b149d744 100644 --- a/view/css/mod_directory.css +++ b/view/css/mod_directory.css @@ -3,8 +3,10 @@ clear: both; } .directory-name { - text-align: center; + float: left; + width: 250px; } + .directory-photo { margin-left: 25px; } diff --git a/view/css/mod_events.css b/view/css/mod_events.css index 0aef13aa6..ba4ec9b61 100644 --- a/view/css/mod_events.css +++ b/view/css/mod_events.css @@ -10,8 +10,3 @@ margin-top: 15px; width: 400px; } - -.required { - color: #ff0000; - font-size: 1.2rem; -} \ No newline at end of file diff --git a/view/css/mod_rate.css b/view/css/mod_rate.css new file mode 100644 index 000000000..58e87b9b4 --- /dev/null +++ b/view/css/mod_rate.css @@ -0,0 +1,8 @@ +#rating-slider { + width: 600px !important; +} + +#rating-text { + width: 400px; + height: 60px; +} diff --git a/view/css/mod_prep.css b/view/css/mod_ratings.css similarity index 75% rename from view/css/mod_prep.css rename to view/css/mod_ratings.css index bb29086da..86d6f5ed3 100644 --- a/view/css/mod_prep.css +++ b/view/css/mod_ratings.css @@ -9,4 +9,8 @@ .directory-item { margin: 10px; +} + +.rating-value { + margin-top: 10px; } \ No newline at end of file diff --git a/view/css/widgets.css b/view/css/widgets.css index 4db87e633..43d132276 100644 --- a/view/css/widgets.css +++ b/view/css/widgets.css @@ -104,3 +104,27 @@ li:hover .group-edit-icon { .chatroomlist td { padding: 0 5px 0; } + +/* ratings */ + +.directory-rating { + float: right; + margin-right: 5px; +} + +.slider-container { + padding: 15px; +} + +.rating-text-label { + margin-top: 30px; +} + +.directory-rating-text { + width: 90%; + margin-left: 5%; +} + +.directory-rating-submit { + margin-top: 15px; +} diff --git a/view/js/mod_directory.js b/view/js/mod_directory.js index 74c8b414d..87e4f92b5 100644 --- a/view/js/mod_directory.js +++ b/view/js/mod_directory.js @@ -3,7 +3,40 @@ function dirdetails(hash) { $.get('dirprofile' + '?f=&hash=' + hash, function( data ) { $.colorbox({ maxWidth: "50%", maxHeight: "75%", html: data }); }); +} + +var ratingVal = 0; +var ratingText = ''; +var currentHash = ''; + +function fetchRatings(hash) { + $.get('prate/'+hash, function(data) { + if(typeof(data.rating) !== 'undefined') { + ratingVal = data.rating; + ratingText = data.rating_text; + } + buildRatingForm(hash); + }); +} + + +function doRatings(hash) { + fetchRatings(hash); +} + +function buildRatingForm(hash) { + var html = '