Index: includes/flag_handler_relationships.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/flag/includes/Attic/flag_handler_relationships.inc,v retrieving revision 1.1.2.9 diff -u -r1.1.2.9 flag_handler_relationships.inc --- includes/flag_handler_relationships.inc 26 Apr 2009 21:35:12 -0000 1.1.2.9 +++ includes/flag_handler_relationships.inc 27 Oct 2009 05:59:20 -0000 @@ -43,11 +43,16 @@ if (!$form['flag']['#options']) { $form = array( 'error' => array( - '#value' => '
' . t('No %type flags exist. You must first create a %type flag before being able to use this relationship type.', array('%type' => $content_type, '@create-url' => url('admin/build/flags'))) . '
', + '#value' => '' . t('No %type flags exist. You must first create a %type flag before being able to use this relationship type.', array('%type' => $content_type, '@create-url' => url('admin/build/flags'))) . '
', ), ); $form_state['no flags exist'] = TRUE; } + if (module_exists('session_api')) { + $form['session_warning'] = array( + '#value' => '' . t('Warning: Adding this relationship for any flag that contains anonymous flagging access will disable page caching for anonymous users when this view is executed. It is recommended to create a dedicated page for views containing anonymous user data.', array('%type' => $content_type, '@create-url' => url('admin/build/flags'))) . '
', + ); + } } function options_validate($form, &$form_state) { @@ -81,6 +86,17 @@ 'value' => '***CURRENT_USER***', 'numeric' => TRUE, ); + if (array_search(DRUPAL_ANONYMOUS_RID, $flag->roles['flag']) !== FALSE) { + // Disable page caching for anonymous users. + $GLOBALS['conf']['cache'] = CACHE_DISABLED; + + // Add in the SID from Session API for anonymous users. + $this->definition['extra'][] = array( + 'field' => 'sid', + 'value' => flag_get_sid(), + 'numeric' => TRUE, + ); + } } parent::query(); } Index: includes/flag.admin.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/flag/includes/Attic/flag.admin.inc,v retrieving revision 1.1.4.2.2.6 diff -u -r1.1.4.2.2.6 flag.admin.inc --- includes/flag.admin.inc 28 Sep 2009 02:05:06 -0000 1.1.4.2.2.6 +++ includes/flag.admin.inc 27 Oct 2009 05:59:19 -0000 @@ -324,19 +324,26 @@ $form['access']['roles'] = array( '#title' => t('Roles that may use this flag'), '#access' => empty($flag->locked['roles']), - '#description' => t('Users may only unflag content if they have access to flag the content initially. Checking authenticated user will allow access for all logged-in users. Anonymous users may not flag content.'), + '#description' => t('Users may only unflag content if they have access to flag the content initially. Checking authenticated user will allow access for all logged-in users.'), '#theme' => 'flag_form_roles', '#weight' => -2, ); + if (module_exists('session_api')) { + $form['access']['roles']['#description'] .= ' ' . t('Support for anonymous users is being provided by Session API.'); + } + else { + $form['access']['roles']['#description'] .= ' ' . t('Anonymous users may flag content if the Session API module is installed.'); + } + $form['access']['roles']['flag'] = array( '#type' => 'checkboxes', - '#options' => user_roles(TRUE), + '#options' => user_roles(!module_exists('session_api')), '#default_value' => $flag->roles['flag'], '#parents' => array('roles', 'flag'), ); $form['access']['roles']['unflag'] = array( '#type' => 'checkboxes', - '#options' => user_roles(TRUE), + '#options' => user_roles(!module_exists('session_api')), '#default_value' => $flag->roles['unflag'], '#parents' => array('roles', 'unflag'), ); Index: includes/flag_handler_field_ops.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/flag/includes/Attic/flag_handler_field_ops.inc,v retrieving revision 1.1.2.5.2.1 diff -u -r1.1.2.5.2.1 flag_handler_field_ops.inc --- includes/flag_handler_field_ops.inc 14 Sep 2009 11:46:40 -0000 1.1.2.5.2.1 +++ includes/flag_handler_field_ops.inc 27 Oct 2009 05:59:19 -0000 @@ -81,6 +81,11 @@ 'value' => '***CURRENT_USER***', 'numeric' => TRUE, ); + $join->extra[] = array( + 'field' => 'sid', + 'value' => flag_get_sid(), + 'numeric' => TRUE, + ); } $flag_table = $this->query->add_table('flag_content', $parent, $join); $this->aliases['is_flagged'] = $this->query->add_field($flag_table, 'content_id'); Index: flag.install =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/flag/Attic/flag.install,v retrieving revision 1.2.2.32.2.6 diff -u -r1.2.2.32.2.6 flag.install --- flag.install 26 Oct 2009 21:47:41 -0000 1.2.2.32.2.6 +++ flag.install 27 Oct 2009 05:59:19 -0000 @@ -86,17 +86,38 @@ $t = get_t(); if ($phase == 'install') { if (!defined('MAINTENANCE_MODE') && _flag_flag_content_installed()) { - $requirements['flag_content_clash']['title'] = $t('Flag'); - $requirements['flag_content_clash']['severity'] = REQUIREMENT_ERROR; - $requirements['flag_content_clash']['description'] = _flag_flag_content_message(); + $requirements['flag_content_clash'] = array( + 'title' => $t('Flag'), + 'severity' => REQUIREMENT_ERROR, + 'description' => _flag_flag_content_message(), + ); } } - if ($phase == 'runtime' && module_exists('translation') && !module_exists('translation_helpers')) { - $requirements['flag_translation']['title'] = $t('Flag'); - $requirements['flag_translation']['severity'] = REQUIREMENT_ERROR; - $requirements['flag_translation']['description'] = $t('To have the flag module work with translations, you need to install and enable the Translation helpers module.'); - $requirements['flag_translation']['value'] = $t('Translation helpers module not found.'); + if ($phase == 'runtime') { + if (module_exists('translation') && !module_exists('translation_helpers')) { + $requirements['flag_translation'] = array( + 'title' => $t('Flag'), + 'severity' => REQUIREMENT_ERROR, + 'description' => $t('To have the flag module work with translations, you need to install and enable the Translation helpers module.'), + 'value' => $t('Translation helpers module not found.'), + ); + } + if (module_exists('session_api')) { + if (file_exists('./robots.txt')) { + $flag_path = url('flag') . '/'; + $robots_string = 'Disallow: ' . $flag_path; + $contents = file_get_contents('./robots.txt'); + if (strpos($contents, $robots_string) === FALSE) { + $requirements['flag_robots'] = array( + 'title' => $t('Flag robots.txt problem'), + 'severity' => REQUIREMENT_WARNING, + 'description' => $t('Flag module may currently be used with anonymous users, however the robots.txt file does not exlude the "@flag-path" path, which may cause search engines to randomly flag and unflag content when they index the site. It is highly recommended to add "@robots-string" to your robots.txt file (located in the root of your Drupal installation).', array('@flag-path' => $flag_path, '@robots-string' => $robots_string)), + 'value' => $t('Search engines flagging content'), + ); + } + } + } } return $requirements; } @@ -202,6 +223,12 @@ 'not null' => TRUE, 'default' => 0, ), + 'sid' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), 'timestamp' => array( 'type' => 'int', 'unsigned' => TRUE, @@ -212,11 +239,11 @@ ), 'primary key' => array('fcid'), 'unique keys' => array( - 'fid_content_id_uid' => array('fid', 'content_id', 'uid'), + 'fid_content_id_uid_sid' => array('fid', 'content_id', 'uid', 'sid'), ), 'indexes' => array( 'content_type_content_id' => array('content_type', 'content_id'), - 'content_type_uid' => array('content_type', 'uid'), + 'content_type_uid_sid' => array('content_type', 'uid', 'sid'), ), ); @@ -502,6 +529,26 @@ return $ret; } +/** + * Add the sid column and unique index on the flag_content table. + */ +function flag_update_6202() { + $ret = array(); + + // Drop the keys affected by the addition of the SID column. + db_drop_unique_key($ret, 'flag_content', 'fid_content_id_uid'); + db_drop_index($ret, 'flag_content', 'content_type_uid'); + + // Add the column. + db_add_field($ret, 'flag_content', 'sid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0)); + + // Re-add the removed keys. + db_add_unique_key($ret, 'flag_content', 'fid_content_id_uid_sid', array('fid', 'content_id', 'uid', 'sid')); + db_add_index($ret, 'flag_content', 'content_type_uid_sid', array('content_type', 'uid', 'sid')); + + return $ret; +} + // This is a replacement for update_sql(). The latter doesn't support placeholders. function _flag_update_sql($sql) { $args = func_get_args(); Index: flag.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/flag/Attic/flag.module,v retrieving revision 1.11.2.72.2.18 diff -u -r1.11.2.72.2.18 flag.module --- flag.module 1 Oct 2009 04:36:44 -0000 1.11.2.72.2.18 +++ flag.module 27 Oct 2009 05:59:19 -0000 @@ -88,6 +88,10 @@ if (module_exists('token')) { include_once $path .'/includes/flag.token.inc'; } + if (module_exists('session_api')) { + // Set the anonymous user SID immediately, in case the user logs in. + flag_set_sid(); + } } /** @@ -280,14 +284,8 @@ ); } } - elseif (isset($form['type']) && isset($form['#node']) - && ($form_id == $form['type']['#value'] .'_node_form')) { - if (!$user->uid) { - return; - } - + elseif (isset($form['type']) && isset($form['#node']) && ($form_id == $form['type']['#value'] .'_node_form')) { $nid = !empty($form['nid']['#value']) ? $form['nid']['#value'] : NULL; - $flags = flag_get_flags('node', $form['type']['#value'], $user); // Filter out flags which need to be included on the node form. @@ -407,6 +405,27 @@ */ function flag_user($op, &$edit, &$account, $category = NULL) { switch ($op) { + case 'login': + // Migrate anonymous flags to this user's account. + if (module_exists('session_api')) { + // The @ symbol suppresses errors if the user flags a piece of content + // they have already flagged as a logged-in user. + @db_query("UPDATE {flag_content} SET uid = %d, sid = 0 WHERE uid = 0 AND sid = %d", $account->uid, flag_get_sid(0)); + // Delete any remaining flags this user had as an anonymous user. + db_query("DELETE FROM {flag_content} WHERE uid = 0 AND sid = %d", flag_get_sid(0)); + // Clean up anonymous cookies. + if (isset($_COOKIE['flags'])) { + setcookie('flags', FALSE, 0, base_path()); + unset($_COOKIE['flags']); + } + foreach ($_COOKIE as $key => $value) { + if (strpos($key, 'flag_global_') === 0) { + setcookie($key, FALSE, 0, base_path()); + unset($_COOKIE[$key]); + } + } + } + break; case 'delete': // Remove flags by this user. $result = db_query("SELECT fc.fid, fc.content_id, c.count FROM {flag_content} fc LEFT JOIN {flag_counts} c ON fc.content_id = c.content_id AND fc.content_type = c.content_type WHERE fc.uid = %d", $account->uid); @@ -448,6 +467,26 @@ } /** + * Implementation of hook_session_api_cleanup(). + * + * Clear out anonymous user flaggings during Session API cleanup. + */ +function flag_session_api_cleanup($arg = 'run') { + // Session API 1.1 version: + if ($arg == 'run') { + $result = db_query("SELECT fc.sid FROM {flag_content} fc LEFT JOIN {session_api} s ON (fc.sid = s.sid) WHERE fc.sid <> 0 AND s.sid IS NULL"); + while ($row = db_fetch_object($result)) { + db_query("DELETE FROM {flag_content} WHERE sid = %d", $row->sid); + } + } + // Session API 1.2+ version. + elseif (is_array($arg)) { + $outdated_sids = $arg; + db_query('DELETE FROM {flag_content} WHERE sid IN (' . implode(',', $outdated_sids) . ')'); + } +} + +/** * Implementation of hook_node_type(). */ function flag_node_type($op, $info) { @@ -640,10 +679,10 @@ $result = db_query("SELECT nid, uid FROM {node} WHERE nid IN ($nids) AND type in ($placeholders)", $flag->types); while ($row = db_fetch_object($result)) { if ($flag->access_author == 'own') { - $access[$row->nid] = $row->uid != $account->uid ? FALSE : $access[$row->nid]; + $access[$row->nid] = $row->uid != $account->uid ? FALSE : NULL; } elseif ($flag->access_author == 'others') { - $access[$row->nid] = $row->uid == $account->uid ? FALSE : $access[$row->nid]; + $access[$row->nid] = $row->uid == $account->uid ? FALSE : NULL; } } } @@ -796,12 +835,14 @@ * See 'flag.tpl.php' for their documentation. */ function template_preprocess_flag(&$variables) { + global $user; static $first_time = TRUE; // Some typing shotcuts: $flag =& $variables['flag']; $action = $variables['action']; $content_id = $variables['content_id']; + $flag_css_name = str_replace('_', '-', $flag->name); // Generate the link URL. $link_type = $flag->get_link_type(); @@ -816,7 +857,9 @@ unset($link['href']); } - if ($flag->link_type == 'toggle' && $first_time) { + // Tell the template file to add JavaScript/CSS if using the toggle link type. + // Anonymous users always need the JavaScript to maintain their flag state. + if (($flag->link_type == 'toggle' || $user->uid == 0) && $first_time) { $variables['setup'] = $first_time; $first_time = FALSE; } @@ -827,23 +870,26 @@ $variables['link_href'] = isset($link['href']) ? check_url(url($link['href'], $link)) : FALSE; $variables['link_text'] = isset($link['title']) ? $link['title'] : $flag->get_label($action . '_short', $content_id); $variables['link_title'] = isset($link['attributes']['title']) ? check_plain($link['attributes']['title']) : check_plain(strip_tags($flag->get_label($action . '_long', $content_id))); - $variables['flag_name_css'] = str_replace('_', '-', $flag->name); $variables['last_action'] = ($action == 'flag' ? 'unflagged' : 'flagged'); + + $variables['flag_wrapper_classes_array'] = array(); + $variables['flag_wrapper_classes_array'][] = 'flag-wrapper'; + $variables['flag_wrapper_classes_array'][] = 'flag-' . $flag_css_name; + $variables['flag_wrapper_classes_array'][] = 'flag-' . $flag_css_name . '-' . $content_id; + $variables['flag_wrapper_classes'] = implode(' ', $variables['flag_wrapper_classes_array']); + $variables['flag_classes_array'] = array(); $variables['flag_classes_array'][] = 'flag'; $variables['flag_classes_array'][] = $variables['action'] .'-action'; $variables['flag_classes_array'][] = 'flag-link-'. $flag->link_type; - if (isset($link['attributes']['class'])) { $variables['flag_classes_array'][] = $link['attributes']['class']; } - if ($variables['after_flagging']) { $inverse_action = ($action == 'flag' ? 'unflag' : 'flag'); $variables['message_text'] = $flag->get_label($inverse_action . '_message', $content_id); $variables['flag_classes_array'][] = $variables['last_action']; } - $variables['flag_classes'] = implode(' ', $variables['flag_classes_array']); } @@ -1194,6 +1240,10 @@ * @param $uid * Optional. The user ID whose flags we're checking. If none given, the * current user will be used. + * @param $sid + * Optional. The user SID (provided by Session API) whose flags we're + * checking. If none given, the current user will be used. The SID is 0 for + * logged in users. * @param $reset * Reset the internal cache and execute the SQL query another time. * @@ -1205,7 +1255,7 @@ * [nid] => [name] => Object from above. * */ -function flag_get_user_flags($content_type, $content_id = NULL, $uid = NULL, $reset = FALSE) { +function flag_get_user_flags($content_type, $content_id = NULL, $uid = NULL, $sid = NULL, $reset = FALSE) { static $flagged_content; if ($reset) { @@ -1216,29 +1266,30 @@ } $uid = !isset($uid) ? $GLOBALS['user']->uid : $uid; + $sid = !isset($sid) ? flag_get_sid($uid) : $sid; if (isset($content_id)) { if (!isset($flagged_content[$uid][$content_type][$content_id])) { $flag_names = _flag_get_flag_names(); - $flagged_content[$uid][$content_type][$content_id] = array(); - $result = db_query("SELECT * FROM {flag_content} WHERE content_type = '%s' AND content_id = %d AND (uid = %d OR uid = 0)", $content_type, $content_id, $uid); + $flagged_content[$uid][$sid][$content_type][$content_id] = array(); + $result = db_query("SELECT * FROM {flag_content} WHERE content_type = '%s' AND content_id = %d AND (uid = %d OR uid = 0) AND sid = %s", $content_type, $content_id, $uid, $sid); while ($flag_content = db_fetch_object($result)) { - $flagged_content[$uid][$content_type][$content_id][$flag_names[$flag_content->fid]] = $flag_content; + $flagged_content[$uid][$sid][$content_type][$content_id][$flag_names[$flag_content->fid]] = $flag_content; } } - return $flagged_content[$uid][$content_type][$content_id]; + return $flagged_content[$uid][$sid][$content_type][$content_id]; } else { - if (!isset($flagged_content[$uid]['all'][$content_type])) { + if (!isset($flagged_content[$uid][$sid]['all'][$content_type])) { $flag_names = _flag_get_flag_names(); - $flagged_content[$uid]['all'][$content_type] = TRUE; - $result = db_query("SELECT * FROM {flag_content} WHERE content_type = '%s' AND (uid = %d OR uid = 0)", $content_type, $uid); + $flagged_content[$uid][$sid]['all'][$content_type] = TRUE; + $result = db_query("SELECT * FROM {flag_content} WHERE content_type = '%s' AND (uid = %d OR uid = 0) AND sid = %s", $content_type, $uid, $sid); while ($flag_content = db_fetch_object($result)) { - $flagged_content[$uid][$content_type]['all'][$flag_names[$flag_content->fid]][$flag_content->content_id] = $flag_content; + $flagged_content[$uid][$sid][$content_type]['all'][$flag_names[$flag_content->fid]][$flag_content->content_id] = $flag_content; } } - return $flagged_content[$uid][$content_type]['all']; + return $flagged_content[$uid][$sid][$content_type]['all']; } } @@ -1333,13 +1384,47 @@ /** * Get a private token used to protect links from spoofing - CSRF. */ -function flag_get_token($nid) { - return drupal_get_token($nid); +function flag_get_token($content_id) { + // Anonymous users get a less secure token, since it must be the same for all + // anonymous users on the entire site to work with page caching. + return ($GLOBALS['user']->uid) ? drupal_get_token($content_id) : md5(drupal_get_private_key() . $content_id); } /** * Check to see if a token value matches the specified node. */ -function flag_check_token($token, $seed) { - return drupal_get_token($seed) == $token; +function flag_check_token($token, $content_id) { + return flag_get_token($content_id) == $token; +} + +/** + * Set the Session ID for a user. Utilizes the Session API module. + * + * This function is only called in flag_init(), to set the current user's + * SID in case the user logs in during this request. + */ +function flag_set_sid($uid = NULL) { + static $sids = array(); + + if (!isset($uid)) { + $uid = $GLOBALS['user']->uid; + } + + if (!isset($sids[$uid])) { + if (module_exists('session_api') && session_api_available() && $uid == 0) { + $sids[$uid] = session_api_get_sid(); + } + else { + $sids[$uid] = 0; + } + } + + return $sids[$uid]; +} + +/** + * Get the Session ID for a user. Utilizes the Session API module. + */ +function flag_get_sid($uid = NULL) { + return flag_set_sid($uid); } Index: flag.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/flag/Attic/flag.inc,v retrieving revision 1.1.2.30.2.12 diff -u -r1.1.2.30.2.12 flag.inc --- flag.inc 4 Oct 2009 02:42:10 -0000 1.1.2.30.2.12 +++ flag.inc 27 Oct 2009 05:59:19 -0000 @@ -399,8 +399,8 @@ $account = $GLOBALS['user']; } - // Anonymous user can't use this system. For now. - if ($account->uid == 0) { + // Anonymous user can't use this system unless Session API is installed. + if ($account->uid == 0 && !module_exists('session_api')) { return FALSE; } @@ -431,7 +431,9 @@ } if (!isset($action)) { - $action = $this->is_flagged($content_id, $account->uid) ? 'unflag' : 'flag'; + $uid = $account->uid; + $sid = flag_get_sid($uid); + $action = $this->is_flagged($content_id, $uid, $sid) ? 'unflag' : 'flag'; } // Base initial access on the user's basic permission to use this flag. @@ -508,6 +510,14 @@ } /** + * Returns TRUE if this flag requires anonymous user cookies. + */ + function uses_anonymous_cookies() { + global $user; + return $user->uid == 0 && variable_get('cache', CACHE_DISABLED) > 0; + } + + /** * Flags, on unflags, an item. * * @param $action @@ -546,20 +556,29 @@ // Clear various caches; We don't want code running after us to report // wrong counts or false flaggings. flag_get_counts(NULL, NULL, TRUE); - flag_get_user_flags(NULL, NULL, NULL, TRUE); + flag_get_user_flags(NULL, NULL, NULL, NULL, TRUE); // Perform the flagging or unflagging of this flag. $uid = $this->global ? 0 : $account->uid; - $flagged = $this->_is_flagged($content_id, $uid); - if ($action == 'unflag' && $flagged) { - $fcid = $this->_unflag($content_id, $uid); - // Let other modules perform actions. - module_invoke_all('flag', 'unflag', $this, $content_id, $account, $fcid); - } - elseif ($action == 'flag' && !$flagged) { - $fcid = $this->_flag($content_id, $uid); - // Let other modules perform actions. - module_invoke_all('flag', 'flag', $this, $content_id, $account, $fcid); + $sid = $this->global ? 0 : flag_get_sid($uid); + $flagged = $this->_is_flagged($content_id, $uid, $sid); + if ($action == 'unflag') { + if ($this->uses_anonymous_cookies()) { + $this->_unflag_anonymous($content_id); + } + if ($flagged) { + $fcid = $this->_unflag($content_id, $uid, $sid); + module_invoke_all('flag', 'unflag', $this, $content_id, $account, $fcid); + } + } + elseif ($action == 'flag') { + if ($this->uses_anonymous_cookies()) { + $this->_flag_anonymous($content_id); + } + if (!$flagged) { + $fcid = $this->_flag($content_id, $uid, $sid); + module_invoke_all('flag', 'flag', $this, $content_id, $account, $fcid); + } } return TRUE; @@ -575,10 +594,12 @@ * Optional. The user ID whose flags we're checking. If none given, the * current user will be used. */ - function is_flagged($content_id, $uid = NULL) { - $uid = !isset($uid) ? $GLOBALS['user']->uid : $uid; + function is_flagged($content_id, $uid = NULL, $sid = NULL) { + $uid = $this->global ? 0 : (!isset($uid) ? $GLOBALS['user']->uid : $uid); + $sid = $this->global ? 0 : (!isset($sid) ? flag_get_sid($uid) : $sid); + // flag_get_user_flags() does caching. - $user_flags = flag_get_user_flags($this->content_type, $content_id, $uid); + $user_flags = flag_get_user_flags($this->content_type, $content_id, $uid, $sid); return isset($user_flags[$this->name]); } @@ -594,8 +615,8 @@ * * @private */ - function _is_flagged($content_id, $uid) { - return db_result(db_query("SELECT fid FROM {flag_content} WHERE fid = %d AND uid = %d AND content_id = %d", $this->fid, $uid, $content_id)); + function _is_flagged($content_id, $uid, $sid) { + return db_result(db_query("SELECT fid FROM {flag_content} WHERE fid = %d AND uid = %d AND sid = %d AND content_id = %d", $this->fid, $uid, $sid, $content_id)); } /** @@ -606,8 +627,8 @@ * * @private */ - function _flag($content_id, $uid) { - db_query("INSERT INTO {flag_content} (fid, content_type, content_id, uid, timestamp) VALUES (%d, '%s', %d, %d, %d)", $this->fid, $this->content_type, $content_id, $uid, time()); + function _flag($content_id, $uid, $sid) { + db_query("INSERT INTO {flag_content} (fid, content_type, content_id, uid, sid, timestamp) VALUES (%d, '%s', %d, %d, %d, %d)", $this->fid, $this->content_type, $content_id, $uid, $sid, time()); $fcid = db_last_insert_id('flag_content', 'fcid'); $this->_update_count($content_id); return $fcid; @@ -621,12 +642,16 @@ * * @private */ - function _unflag($content_id, $uid) { - $fcid = db_result(db_query("SELECT fcid FROM {flag_content} WHERE fid = %d AND uid = %d AND content_id = %d", $this->fid, $uid, $content_id)); + function _unflag($content_id, $uid, $sid) { + $fcid = db_result(db_query("SELECT fcid FROM {flag_content} WHERE fid = %d AND content_id = %d AND uid = %d AND sid = %d", $this->fid, $content_id, $uid, $sid)); if ($fcid) { db_query("DELETE FROM {flag_content} WHERE fcid = %d", $fcid); $this->_update_count($content_id); } + // Remove anonymous cookies (if any). + if (isset($_COOKIE['flag'][$this->name . '_' . $content_id])) { + unset($_COOKIE['flags'][$this->name . '_' . $content_id]); + } return $fcid; } @@ -644,6 +669,78 @@ } /** + * Set a cookie for anonymous users to record their flagging. + * + * @private + */ + function _flag_anonymous($content_id) { + // Global flags persist for the length of the minimum cache lifetime. + if ($this->global) { + $cookie_key = 'flag_global_' . str_replace('_', '-', $this->name) . '_' . $content_id; + $cookie_lifetime = (variable_get('cache', 0) > CACHE_DISABLED) ? variable_get('cache_lifetime', 0) : -1; + // Do not let the cookie lifetime be 0 (which is the no cache limit on + // anonymous page caching), since it would expire immediately. Usually + // the no cache limit means caches are cleared on cron, which usually runs + // at least once an hour. + if ($cookie_lifetime == 0) { + $cookie_lifetime = 3600; + } + setcookie($cookie_key, 1, time() + $cookie_lifetime, base_path()); + $_COOKIE[$cookie_key] = 1; + } + // The anonymous per-user flags are stored in a single cookie, so that all + // of them persist as long as the Drupal cookie lifetime. + else { + $cookie_flags = isset($_COOKIE['flags']) ? $_COOKIE['flags'] : ''; + $cookie_flags = explode(' ', $cookie_flags); + $cookie_key = str_replace('_', '-', $this->name) . '_' . $content_id; + $cookie_lifetime = min((int) ini_get('session.cookie_lifetime'), (int) ini_get('session.gc_maxlifetime')); + if (array_search($cookie_key, $cookie_flags) === FALSE) { + $cookie_flags[] = $cookie_key; + $cookie_flags = implode(' ', array_filter($cookie_flags)); + setcookie('flags', $cookie_flags, time() + $cookie_lifetime, base_path()); + $_COOKIE['flags'] = $cookie_flags; + } + } + } + + /** + * Remove the cookie for anonymous users to record their unflagging. + * + * @private + */ + function _unflag_anonymous($content_id) { + // Global flags are easy, just delete the global cookie. + if ($this->global) { + $cookie_key = 'flag_global_' . str_replace('_', '-', $this->name) . '_' . $content_id; + $cookie_lifetime = (variable_get('cache', 0) > CACHE_DISABLED) ? variable_get('cache_lifetime', 0) : -1; + // Do not let the cookie lifetime be 0 (which is the no cache limit on + // anonymous page caching), since it would expire immediately. Usually + // the no cache limit means caches are cleared on cron, which usually runs + // at least once an hour. + if ($cookie_lifetime == 0) { + $cookie_lifetime = 3600; + } + setcookie($cookie_key, 0, time() + $cookie_lifetime, base_path()); + $_COOKIE[$cookie_key] = 0; + } + // User flags, update the single cookie for all user-data. + else { + $cookie_flags = isset($_COOKIE['flags']) ? $_COOKIE['flags'] : ''; + $cookie_flags = explode(' ', $cookie_flags); + $cookie_key = str_replace('_', '-', $this->name) . '_' . $content_id; + $cookie_lifetime = min((int) ini_get('session.cookie_lifetime'), (int) ini_get('session.gc_maxlifetime')); + $cookie_index = array_search($cookie_key, $cookie_flags); + if ($cookie_index !== FALSE) { + unset($cookie_flags[$cookie_index]); + $cookie_flags = implode(' ', $cookie_flags); + setcookie('flags', $cookie_flags, $cookie_lifetime, base_path()); + $_COOKIE['flags'] = $cookie_flags; + } + } + } + + /** * Returns the number of times an item is flagged. * * Thanks to using a cache, inquiring several different flags about the same @@ -657,10 +754,10 @@ /** * Returns the number of items a user has flagged. * - * For global flags, pass '0' as the user ID. + * For global flags, pass '0' as the user ID and session ID. */ - function get_user_count($uid) { - return db_result(db_query('SELECT COUNT(*) FROM {flag_content} WHERE fid = %d AND uid = %d', $this->fid, $uid)); + function get_user_count($uid, $sid) { + return db_result(db_query('SELECT COUNT(*) FROM {flag_content} WHERE fid = %d AND uid = %d AND sid = %d', $this->fid, $uid, $sid)); } /** @@ -846,6 +943,8 @@ else { $this->insert(); } + // Clear the page cache for anonymous users. + cache_clear_all('*', 'cache_page', TRUE); } /** @@ -921,6 +1020,19 @@ * For parameters docmentation, see theme_flag(). */ function theme($action, $content_id, $after_flagging = FALSE) { + static $js_added = array(); + + // If the flagging user is anonymous and the page cache is enabled, always + // show the "flag" link, and then set the unflag link through JavaScript. + if ($this->uses_anonymous_cookies() && !$after_flagging) { + $js_action = $this->global ? ($action == 'flag' ? 'unflag' : 'flag') : 'flag'; + if (!isset($js_added[$this->name . '_' . $content_id])) { + $js_added[$this->name . '_' . $content_id] = TRUE; + $js_template = theme($this->theme_suggestions(), $this, 'unflag', $content_id, $after_flagging); + drupal_add_js(array('flag' => array('templates' => array($this->name . '_' . $content_id => $js_template))), 'setting'); + } + } + return theme($this->theme_suggestions(), $this, $action, $content_id, $after_flagging); } @@ -1060,10 +1172,9 @@ return parent::flag($action, $content_id, $account, $skip_permission_check); } - - function is_flagged($content_id, $uid = NULL) { + function is_flagged($content_id, $uid = NULL, $sid = NULL) { $content_id = $this->get_translation_id($content_id); - return parent::is_flagged($content_id, $uid); + return parent::is_flagged($content_id, $uid, $sid); } function get_labels_token_types() { Index: README.txt =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/flag/README.txt,v retrieving revision 1.1.4.6 diff -u -r1.1.4.6 README.txt --- README.txt 7 Oct 2008 16:41:26 -0000 1.1.4.6 +++ README.txt 27 Oct 2009 05:59:18 -0000 @@ -45,15 +45,15 @@ provides many views filters, fields, and sort criteria to make all sorts of displays possible relating to the number of times an item has been flagged. -This module was formerly known as Views Bookmark. +This module was formerly known as Views Bookmark, which was originally was +written by Earl Miles. Flag was written by Nathan Haug and mystery man Mooffie. -Ongoing development by Nathan Haug and mystery man Mooffie. - -Originally written by Earl Miles. +This module built by robots: http://www.lullabot.com Recommended Modules ------------------- - Views +- Session API Installation ------------ @@ -61,6 +61,11 @@ 2) Enable the module using Administer -> Modules (/admin/build/modules) +Optional Installation +--------------------- +1) The ability for anonymous users to flag content is provided by the Session + API module, available at http://drupal.org/project/session_api. + Configuration ------------- The configuration for Flag is spread between Views configuration Index: theme/flag.js =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/flag/theme/Attic/flag.js,v retrieving revision 1.1.2.7.2.3 diff -u -r1.1.2.7.2.3 flag.js --- theme/flag.js 28 Sep 2009 18:56:49 -0000 1.1.2.7.2.3 +++ theme/flag.js 27 Oct 2009 05:59:20 -0000 @@ -8,6 +8,9 @@ * specifically of the element, we say "element" or "the element". */ +/** + * The main behavior to perform AJAX toggling of links. + */ Drupal.flagLink = function(context) { /** * Helper function. Updates a link's HTML with a new one. @@ -98,8 +101,101 @@ $('a.flag-link-toggle:not(.flag-processed)').addClass('flag-processed').click(flagClick); }; +/** + * A behvior specifically for anonymous users. Update links to the proper state. + */ +Drupal.flagAnonymousLinks = function(context) { + // Swap in current links. Cookies are set by PHP's setcookie() upon flagging. + + // Build a list of user-flags. + var userFlags = Drupal.flagCookie('flags'); + if (userFlags) { + userFlags = userFlags.split('+'); + for (var n in userFlags) { + var flagInfo = userFlags[n].split('_'); + var flagName = flagInfo[0]; + var contentId = flagInfo[1]; + // User flags always default to off and the JavaScript toggles them on. + $('.flag-' + flagName + '-' + contentId, context).after(Drupal.settings.flag.templates[flagName + '_' + contentId]).remove(); + } + } + + // Build a list of global flags. + var globalFlags = document.cookie.match(/flag_global_([a-z0-9\-]+)_([0-9]+)=([01])/ig); + if (globalFlags) { + for (var n in globalFlags) { + var flagInfo = globalFlags[n].match(/flag_global_([a-z0-9\-]+)_([0-9]+)=([01])/i); + var flagName = flagInfo[1]; + var contentId = flagInfo[2]; + var flagState = (flagInfo[3] == '1') ? 'flag' : 'unflag'; + // Global flags are tricky, they may or may not be flagged in the page + // cache. The template always contains the opposite of the current state. + // So when checking global flag cookies, we need to make sure that we + // don't swap out the link when it's already in the correct state. + $('.flag-' + flagName + '-' + contentId, context).each(function() { + if ($(this).find('.' + flagState + '-action').size()) { + $(this).after(Drupal.settings.flag.templates[flagName + '_' + contentId]).remove(); + } + }); + } + } +} + +/** + * Utility function used to set Flag cookies. + * + * Note this is a direct copy of the jQuery cookie library. + * Written by Klaus Hartl. + */ +Drupal.flagCookie = function(name, value, options) { + if (typeof value != 'undefined') { // name and value given, set cookie + options = options || {}; + if (value === null) { + value = ''; + options = $.extend({}, options); // clone object since it's unexpected behavior if the expired property were changed + options.expires = -1; + } + var expires = ''; + if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) { + var date; + if (typeof options.expires == 'number') { + date = new Date(); + date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); + } else { + date = options.expires; + } + expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE + } + // NOTE Needed to parenthesize options.path and options.domain + // in the following expressions, otherwise they evaluate to undefined + // in the packed version for some reason... + var path = options.path ? '; path=' + (options.path) : ''; + var domain = options.domain ? '; domain=' + (options.domain) : ''; + var secure = options.secure ? '; secure' : ''; + document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join(''); + } else { // only name given, get cookie + var cookieValue = null; + if (document.cookie && document.cookie != '') { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = jQuery.trim(cookies[i]); + // Does this cookie string begin with the name we want? + if (cookie.substring(0, name.length + 1) == (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; + } +}; Drupal.behaviors.flagLink = function(context) { + // For anonymous users, swap out links with their current state for the user. + if (Drupal.settings.flag && Drupal.settings.flag.templates) { + Drupal.flagAnonymousLinks(context); + } + // On load, bind the click behavior for all links on the page. Drupal.flagLink(context); }; Index: theme/flag.tpl.php =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/flag/theme/Attic/flag.tpl.php,v retrieving revision 1.1.2.7.2.1 diff -u -r1.1.2.7.2.1 flag.tpl.php --- theme/flag.tpl.php 28 Sep 2009 02:05:06 -0000 1.1.2.7.2.1 +++ theme/flag.tpl.php 27 Oct 2009 05:59:20 -0000 @@ -40,7 +40,7 @@ drupal_add_js(drupal_get_path('module', 'flag') .'/theme/flag.js'); } ?> - +