loki_website/0000755000004100000410000000000011627305430013567 5ustar www-datawww-dataloki_website/xmlrpc.php0000644000004100000410000000054010346625770015615 0ustar www-datawww-data Order allow,deny # Don't show directory listings for URLs which map to a directory. Options -Indexes # Follow symbolic links in this directory. Options +FollowSymLinks # Customized error messages. ErrorDocument 404 /index.php # Set the default handler. DirectoryIndex index.php # Override PHP settings. More in sites/default/settings.php # but the following cannot be changed at runtime. # PHP 4, Apache 1. php_value magic_quotes_gpc 0 php_value register_globals 0 php_value session.auto_start 0 php_value mbstring.http_input pass php_value mbstring.http_output pass php_value mbstring.encoding_translation 0 # PHP 4, Apache 2. php_value magic_quotes_gpc 0 php_value register_globals 0 php_value session.auto_start 0 php_value mbstring.http_input pass php_value mbstring.http_output pass php_value mbstring.encoding_translation 0 # PHP 5, Apache 1 and 2. php_value magic_quotes_gpc 0 php_value register_globals 0 php_value session.auto_start 0 php_value mbstring.http_input pass php_value mbstring.http_output pass php_value mbstring.encoding_translation 0 # Requires mod_expires to be enabled. # Enable expirations. ExpiresActive On # Cache all files for 2 weeks after access (A). ExpiresDefault A1209600 # Do not cache dynamically generated pages. ExpiresByType text/html A1 # Various rewrite rules. RewriteEngine on # If your site can be accessed both with and without the 'www.' prefix, you # can use one of the following settings to redirect users to your preferred # URL, either WITH or WITHOUT the 'www.' prefix. Choose ONLY one option: # # To redirect all users to access the site WITH the 'www.' prefix, # (http://example.com/... will be redirected to http://www.example.com/...) # adapt and uncomment the following: # RewriteCond %{HTTP_HOST} ^example\.com$ [NC] # RewriteRule ^(.*)$ http://www.example.com/$1 [L,R=301] # # To redirect all users to access the site WITHOUT the 'www.' prefix, # (http://www.example.com/... will be redirected to http://example.com/...) # adapt and uncomment the following: # RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC] # RewriteRule ^(.*)$ http://example.com/$1 [L,R=301] # Modify the RewriteBase if you are using Drupal in a subdirectory and # the rewrite rules are not working properly. #RewriteBase /drupal # Rewrite old-style URLs of the form 'node.php?id=x'. #RewriteCond %{REQUEST_FILENAME} !-f #RewriteCond %{REQUEST_FILENAME} !-d #RewriteCond %{QUERY_STRING} ^id=([^&]+)$ #RewriteRule node.php index.php?q=node/view/%1 [L] # Rewrite old-style URLs of the form 'module.php?mod=x'. #RewriteCond %{REQUEST_FILENAME} !-f #RewriteCond %{REQUEST_FILENAME} !-d #RewriteCond %{QUERY_STRING} ^mod=([^&]+)$ #RewriteRule module.php index.php?q=%1 [L] # Rewrite current-style URLs of the form 'index.php?q=x'. RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ index.php?q=$1 [L,QSA] # $Id: .htaccess,v 1.81.2.3 2007/09/21 12:24:22 drumm Exp $ loki_website/modules/0000755000004100000410000000000010744012100015223 5ustar www-datawww-dataloki_website/modules/drupal/0000755000004100000410000000000010744012100016512 5ustar www-datawww-dataloki_website/modules/drupal/drupal.module0000644000004100000410000004226510543315756021245 0ustar www-datawww-data'. t('The Drupal module uses the XML-RPC network communication protocol to connect your site with a central server that maintains a directory of client sites.') .'

'; $output .= t('

Enabling the Drupal module will allow you to:

'); $output .= '

'. t('The Drupal module administration page allows you to set the xml-rpc server page and other related options.') .'

'; $output .= t('

If you maintain a directory of sites, you can list them on a page using the drupal_client_page() function. Sample instructions:

'); $output .= '

'. t('For more information please read the configuration and customization handbook Drupal page.', array('@drupal' => 'http://drupal.org/handbook/modules/drupal/')) .'

'; return $output; case 'admin/settings/distributed-authentication': return '

'. t('Using this your site can "call home" to another Drupal server. By calling home to drupal.org and sending a list of your installed modules and themes, you help rank projects on drupal.org and so assist all Drupal administrators to find the best components for meeting their needs. If you want to register with a different server, you can change the Drupal XML-RPC server setting -- but the server has to be able to handle Drupal XML. Some XML-RPC servers may present directories of all registered sites. To get all your site information listed, go to the site information settings page and set the site name, the e-mail address, the slogan, and the mission statement.', array('@site-settings' => url('admin/settings/site-information'))) .'

'; case 'user/help#drupal': return variable_get('drupal_authentication_service', 0) ? t('

Drupal is the name of the software that powers %this-site. There are Drupal websites all over the world, and many of them share their registration databases so that users may freely log in to any Drupal site using a single Drupal ID.

So please feel free to log in to your account here at %this-site with a username from another Drupal site. The format of a Drupal ID is similar to an e-mail address: username@server. An example of a valid Drupal ID is mwlily@drupal.org.

', array('@Drupal' => 'http://drupal.org', '%this-site' => variable_get('site_name', 'Drupal'))) : ''; } } function drupal_sites_registry_settings() { // Check if all required fields are present if ((variable_get('site_name', 'Drupal') == 'Drupal') || (variable_get('site_name', 'Drupal') == '')) { form_set_error('drupal_directory', t('You must set the name of your site on the administer » settings » site information page.', array('@url' => url('admin/settings/site-information')))); } else if (variable_get('site_mail', ini_get('sendmail_from')) == '') { form_set_error('drupal_directory', t('You must set an e-mail address for your site on the site information settings page.', array('@url' => url('admin/settings/site-information')))); } else if (variable_get('site_slogan', '') == '') { form_set_error('drupal_directory', t('You must set your site slogan on the site information settings page.', array('@url' => url('admin/settings/site-information')))); } else if (variable_get('site_mission', '') == '') { form_set_error('drupal_directory', t('You must set your site mission on the site information settings page.' , array('@url' => url('admin/settings/site-information')))); } $options = array('1' => t('Enabled'), '0' => t('Disabled')); $form['drupal_register'] = array( '#type' => 'radios', '#title' => t('Register with a Drupal server'), '#default_value' => variable_get('drupal_register', 0), '#options' => $options, '#description' => t("If enabled, your Drupal site will register itself with the specified Drupal XML-RPC server. For this to work properly, you must set your site's name, e-mail address, slogan and mission statement. When the Drupal XML-RPC server field is set to %drupal-xml-rpc, your web site will register itself with drupal.org. Requires the cron feature to be enabled.", array("%drupal-xml-rpc" => "http://drupal.org/xmlrpc.php")) ); $form['drupal_server'] = array( '#type' => 'textfield', '#title' => t('Drupal XML-RPC server'), '#default_value' => variable_get('drupal_server', 'http://drupal.org/xmlrpc.php'), '#description' => t('The URL of the Drupal XML-RPC server you wish to register with.') ); $form['drupal_system'] = array( '#type' => 'radios', '#title' => t('Send system information'), '#default_value' => variable_get('drupal_system', 0), '#options' => $options, '#description' => t("If enabled, your site will send information on its installed components (modules, themes, and theme engines). This information can help in compiling statistics on usage of Drupal projects.") ); $form['drupal_statistics'] = array( '#type' => 'radios', '#title' => t('Send statistics'), '#default_value' => variable_get('drupal_statistics', 0), '#options' => $options, '#description' => t("If enabled, your site will send summary statistics on the number of registered users and the total number of posts. No private information will be sent. These data help to improve the ranking statistics of Drupal projects.") ); ; $form['drupal_client_service'] = array( '#type' => 'radios', '#title' => t('Allow other Drupal sites to register'), '#default_value' => variable_get('drupal_client_service', 0), '#options' => $options, '#description' => t('If enabled, your Drupal site will allow other sites to register with your site and send information to this site. This functionality can be used to maintain a list of related sites.') ); return system_settings_form($form); } function drupal_distributed_authentication_settings() { $options = array('1' => t('Enabled'), '0' => t('Disabled')); $form['drupal_authentication_service'] = array( '#type' => 'radios', '#title' => t('Authentication service'), '#default_value' => variable_get('drupal_authentication_service', 0), '#options' => $options, '#description' => t('If enabled, your Drupal site will accept logins with the user names of other Drupal sites, and likewise provide authentication for users logging into other Drupal sites, based on their user accounts here.') ); $form['drupal_default_da_server'] = array( '#type' => 'textfield', '#title' => t('Default authentication server'), '#default_value' => variable_get('drupal_default_da_server', ''), '#description' => t('The URL of the default Drupal authentication server. Omit the %http prefix (e.g. drupal.org, www.example.com, etc.). If the authentication service has been enabled, users registered at the server specified here, will not need to append the server to their user name when logging into your site. This enables users to provide a briefer, more familiar username in the login form.', array('%http' => 'http')) ); $form['drupal_default_da_server_only'] = array( '#type' => 'radios', '#title' => t('Only allow authentication from default server'), '#default_value' => variable_get('drupal_default_da_server_only', 0), '#options' => $options, '#description' => t("Only accept remote logins from the above specified default authentication server and not from any other server. Useful when an external system is the solitary authority on user accounts for this site. A common usage is to enable this setting and also enable an authentication module which talks to your company's directory server.") ); return system_settings_form($form); } /** * Implementation of hook_cron(); handles pings to and from the site. */ function drupal_cron() { if (time() - variable_get('cron_last', 0) > 21600) { // If this site acts as a Drupal XML-RPC server, delete the sites that // stopped sending "ping" messages. if (variable_get('drupal_client_service', 0)) { $result = db_query("SELECT cid FROM {client} WHERE changed < %d", time() - 259200); while ($client = db_fetch_object($result)) { db_query("DELETE FROM {client_system} WHERE cid = %d", $client->cid); db_query("DELETE FROM {client} WHERE cid = %d", $client->cid); } } // If this site acts as a Drupal XML-RPC client, send a message to the // Drupal XML-RPC server. if (variable_get('drupal_register', 0) && variable_get('drupal_server', 0)) { drupal_notify(variable_get('drupal_server', '')); } } } /** * Callback function from drupal_xmlrpc() called when another site pings this one. */ function drupal_client_ping($client, $system) { /* ** Parse our parameters: */ foreach (array('link', 'name', 'mail', 'slogan', 'mission') as $key) { $client[$key] = strip_tags($client[$key]); } /* ** Update the data in our database and send back a reply: */ if ($client['link'] && $client['name'] && $client['mail'] && $client['slogan'] && $client['mission']) { $result = db_query("SELECT cid FROM {client} WHERE link = '%s'", $client['link']); if (db_num_rows($result)) { $record = db_fetch_object($result); $client['cid'] = $record->cid; // We have an existing record. db_query("UPDATE {client} SET link = '%s', name = '%s', mail = '%s', slogan = '%s', mission = '%s', users = %d, nodes = %d, version = '%s', changed = '%s' WHERE cid = %d", $client['uid'], $client['link'], $client['name'], $client['mail'], $client['slogan'], $client['mission'], $client['users'], $client['nodes'], $client['version'], time(), $client['cid']); } else { $client['cid'] = db_next_id('{client}_cid'); db_query("INSERT INTO {client} (cid, link, name, mail, slogan, mission, users, nodes, version, created, changed) VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')", $client['cid'], $client['link'], $client['name'], $client['mail'], $client['slogan'], $client['mission'], $client['users'], $client['nodes'], $client['version'], time(), time()); } if (is_array($system)) { db_query("DELETE FROM {client_system} WHERE cid = %d", $client['cid']); foreach ($system as $item) { db_query("INSERT INTO {client_system} (cid, name, type) VALUES (%d, '%s', '%s')", $client['cid'], $item['name'], $item['type']); } } watchdog('client ping', t('Ping from %name (%link).', array('%name' => $client['name'], '%link' => $client['link'])), WATCHDOG_NOTICE, 'view'); return TRUE; } else { return 0; } } /** * Formats a list of all clients. * * This function may be called from a custom page on sites that are * Drupal directory servers. */ function drupal_client_page($sort = 'name') { $result = db_query('SELECT * FROM {client} ORDER BY %s', $sort); $clients = array(); while ($client = db_fetch_object($result)) { $clients[] = $client; } return theme('client_list', $clients); } /** * Theme a client list. */ function theme_client_list($clients) { // Note: All fields except the mission are treated as plain-text. // The mission is stripped of any HTML tags to keep the output simple and consistent. $output = "\n
\n"; foreach ($clients as $client) { $output .= '
' . check_plain($client->name) . ' - ' . check_plain($client->slogan) . "
\n"; $output .= '
' . strip_tags($client->mission) . "
\n"; } $output .= "
\n"; return $output; } /** * Implementation of hook_xmlrpc(). */ function drupal_xmlrpc() { $xmlrpc = array(); if (variable_get('drupal_client_service', 0)) { $xmlrpc[] = array( 'drupal.client.ping', 'drupal_client_ping', array('array', 'array', 'array'), t('Handling ping request') ); } if (variable_get('drupal_authentication_service', 0)) { $xmlrpc[] = array( 'drupal.login', 'drupal_login', array('int', 'string', 'string'), t('Logging into a Drupal site') ); } return $xmlrpc; } /** * Sends a ping to the Drupal directory server. */ function drupal_notify($server) { global $base_url; $client = array( 'link' => $base_url, 'name' => variable_get('site_name', ''), 'mail' => variable_get('site_mail', ''), 'slogan' => variable_get('site_slogan', ''), 'mission' => variable_get('site_mission', ''), 'version' => VERSION ); if (variable_get('drupal_system', 0)) { $system = array(); $result = db_query("SELECT name, type FROM {system} WHERE status = 1"); while ($item = db_fetch_array($result)) { $system[] = $item; } } if (variable_get('drupal_statistics', 0)) { $users = db_fetch_object(db_query("SELECT COUNT(uid) AS count FROM {users}")); $client['users'] = $users->count; $nodes = db_fetch_object(db_query("SELECT COUNT(nid) AS count FROM {node}")); $client['nodes'] = $nodes->count; } $result = xmlrpc($server, 'drupal.client.ping', $client, $system); if ($result === FALSE) { watchdog('server ping', t('Failed to notify %server; error code: %errno; error message: %error_msg.', array('%server' => $server, '%errno' => xmlrpc_errno(), '%error_msg' => xmlrpc_error_msg())), WATCHDOG_WARNING); } } /** * Implementation of hook_info(). */ function drupal_info($field = 0) { $info['name'] = 'Drupal'; $info['protocol'] = 'XML-RPC'; if ($field) { return $info[$field]; } else { return $info; } } /** * Implementation of hook_auth(). */ function drupal_auth($username, $password, $server = FALSE) { if (variable_get('drupal_authentication_service', 0)) { if (!$server) { $server = variable_get('drupal_default_da_server', ''); } else if (variable_get('drupal_default_da_server_only', 0)) { if (variable_get('drupal_default_da_server', '') != $server) { return; } } if (!empty($server)) { $result = xmlrpc("http://$server/xmlrpc.php", 'drupal.login', $username, $password); if ($result === FALSE) { drupal_set_message(t('Error %code: %message', array('%code' => xmlrpc_errno(), '%message' => xmlrpc_error_msg())), 'error'); } else { return $result; } } } } /** * Implementation of hook_menu(). */ function drupal_menu($may_cache) { $items = array(); if ($may_cache) { $items[] = array('path' => 'admin/settings/sites-registry', 'title' => t('Sites registry'), 'description' => t('Register with another Drupal site (drupal.org by default) for statistics sharing, or set up your server to be a central server for registrations.'), 'callback' => 'drupal_get_form', 'callback arguments' => array('drupal_sites_registry_settings'), 'access' => user_access('administer site configuration')); $items[] = array('path' => 'admin/settings/distributed-authentication', 'title' => t('Distributed authentication'), 'description' => t('Allow your site to accept logins from other Drupal sites such as drupal.org.'), 'callback' => 'drupal_get_form', 'callback arguments' => array('drupal_distributed_authentication_settings'), 'access' => user_access('administer site configuration'));; if (variable_get('drupal_authentication_service', 0)) { $items[] = array('path' => 'drupal', 'title' => t('Drupal'), 'callback' => 'drupal_page_help', 'access' => TRUE, 'type' => MENU_SUGGESTED_ITEM ); } } return $items; } /** * Menu callback; print Drupal-authentication-specific information from user/help. */ function drupal_page_help() { return drupal_help('user/help#drupal'); } /** * Callback function from drupal_xmlrpc() for authenticating remote clients. * * Remote clients are usually other Drupal instances. */ function drupal_login($username, $password) { if (variable_get('drupal_authentication_service', 0)) { if ($user = user_load(array('name' => $username, 'pass' => $password, 'status' => 1))) { return $user->uid; } else { return 0; } } } loki_website/modules/drupal/drupal.info0000644000004100000410000000070610741515024020673 0ustar www-datawww-data; $Id: drupal.info,v 1.3 2006/11/21 20:55:34 dries Exp $ name = Drupal description = Lets you register your site with a central server and improve ranking of Drupal projects by posting information on your installed modules and themes; also enables users to log in using a Drupal ID. package = Core - optional version = VERSION ; Information added by drupal.org packaging script on 2008-01-10 version = "5.6" project = "drupal" datestamp = "1200003604" loki_website/modules/drupal/drupal.install0000644000004100000410000000451010475761730021416 0ustar www-datawww-data= 0), link varchar(255) NOT NULL default '', name varchar(128) NOT NULL default '', mail varchar(128) NOT NULL default '', slogan text NOT NULL, mission text NOT NULL, users int NOT NULL default '0', nodes int NOT NULL default '0', version varchar(35) NOT NULL default'', created int NOT NULL default '0', changed int NOT NULL default '0', PRIMARY KEY (cid) )"); db_query("CREATE TABLE {client_system} ( cid int NOT NULL default '0', name varchar(255) NOT NULL default '', type varchar(255) NOT NULL default '', PRIMARY KEY (cid,name) )"); break; } } /** * Implementation of hook_uninstall(). */ function drupal_uninstall() { db_query('DROP TABLE {client}'); db_query('DROP TABLE {client_system}'); variable_del('drupal_authentication_service'); variable_del('drupal_directory'); variable_del('drupal_register'); variable_del('drupal_server'); variable_del('drupal_system'); variable_del('drupal_statistics'); variable_del('drupal_client_service'); variable_del('drupal_default_da_server'); variable_del('drupal_default_da_server_only'); } loki_website/modules/tracker/0000755000004100000410000000000010744012100016656 5ustar www-datawww-dataloki_website/modules/tracker/tracker.info0000644000004100000410000000051110741515024021175 0ustar www-datawww-data; $Id: tracker.info,v 1.3.2.1 2007/07/09 03:33:58 drumm Exp $ name = Tracker description = Enables tracking of recent posts for users. dependencies = comment package = Core - optional version = VERSION ; Information added by drupal.org packaging script on 2008-01-10 version = "5.6" project = "drupal" datestamp = "1200003604" loki_website/modules/tracker/tracker.module0000644000004100000410000001276210652171442021545 0ustar www-datawww-data'. t('The tracker module displays the most recently added or updated content to the website allowing users to see the most recent contributions. The tracker module provides user level tracking for those who like to follow the contributions of particular authors.') .'

'; $output .= '

'. t('The "recent posts" page is available via a link in the navigation menu block and contains a reverse chronological list of new and recently-updated content. The table displays the content type, the title, the author\'s name, how many comments that item has received, and when it was last updated. Updates include any changes to the text, either by the original author or someone else, as well as any new comments added to an item. To use the tracker module to watch for a user\'s updated content, click on that user\'s profile, then the track tab.') .'

'; $output .= '

'. t('For more information please read the configuration and customization handbook Tracker page.', array('@tracker' => 'http://drupal.org/handbook/modules/tracker/')) .'

'; return $output; } } /** * Implementation of hook_menu(). */ function tracker_menu($may_cache) { global $user; $items = array(); if ($may_cache) { $items[] = array('path' => 'tracker', 'title' => t('Recent posts'), 'callback' => 'tracker_page', 'access' => user_access('access content'), 'weight' => 1); if ($user->uid) { $items[] = array('path' => 'tracker/all', 'title' => t('All recent posts'), 'type' => MENU_DEFAULT_LOCAL_TASK); $items[] = array('path' => 'tracker/'. $user->uid, 'title' => t('My recent posts'), 'type' => MENU_LOCAL_TASK); } } else { if (arg(0) == 'user' && is_numeric(arg(1))) { $items[] = array('path' => 'user/'. arg(1) .'/track', 'title' => t('Track'), 'callback' => 'tracker_track_user', 'access' => user_access('access content'), 'type' => MENU_IS_LOCAL_TASK); $items[] = array('path' => 'user/'. arg(1) .'/track/posts', 'title' => t('Track posts'), 'type' => MENU_DEFAULT_LOCAL_TASK); } } return $items; } /** * Menu callback. Prints a listing of active nodes on the site. */ function tracker_track_user() { if ($account = user_load(array('uid' => arg(1)))) { if ($account->status || user_access('administer users')) { drupal_set_title(check_plain($account->name)); return tracker_page($account->uid); } else { drupal_access_denied(); } } else { drupal_not_found(); } } /** * Menu callback. Prints a listing of active nodes on the site. */ function tracker_page($uid = 0) { // Add CSS drupal_add_css(drupal_get_path('module', 'tracker') .'/tracker.css', 'module', 'all', FALSE); // TODO: These queries are very expensive, see http://drupal.org/node/105639 if ($uid) { $sql = 'SELECT DISTINCT(n.nid), n.title, n.type, n.changed, n.uid, u.name, GREATEST(n.changed, l.last_comment_timestamp) AS last_updated, l.comment_count FROM {node} n INNER JOIN {node_comment_statistics} l ON n.nid = l.nid INNER JOIN {users} u ON n.uid = u.uid LEFT JOIN {comments} c ON n.nid = c.nid AND (c.status = %d OR c.status IS NULL) WHERE n.status = 1 AND (n.uid = %d OR c.uid = %d) ORDER BY last_updated DESC'; $sql = db_rewrite_sql($sql); $sql_count = 'SELECT COUNT(DISTINCT(n.nid)) FROM {node} n LEFT JOIN {comments} c ON n.nid = c.nid AND (c.status = %d OR c.status IS NULL) WHERE n.status = 1 AND (n.uid = %d OR c.uid = %d)'; $sql_count = db_rewrite_sql($sql_count); $result = pager_query($sql, 25, 0, $sql_count, COMMENT_PUBLISHED, $uid, $uid); } else { $sql = 'SELECT DISTINCT(n.nid), n.title, n.type, n.changed, n.uid, u.name, GREATEST(n.changed, l.last_comment_timestamp) AS last_updated, l.comment_count FROM {node} n INNER JOIN {users} u ON n.uid = u.uid INNER JOIN {node_comment_statistics} l ON n.nid = l.nid WHERE n.status = 1 ORDER BY last_updated DESC'; $sql = db_rewrite_sql($sql); $sql_count = 'SELECT COUNT(n.nid) FROM {node} n WHERE n.status = 1'; $sql_count = db_rewrite_sql($sql_count); $result = pager_query($sql, 25, 0, $sql_count); } $rows = array(); while ($node = db_fetch_object($result)) { // Determine the number of comments: $comments = 0; if ($node->comment_count) { $comments = $node->comment_count; if ($new = comment_num_new($node->nid)) { $comments .= '
'; $comments .= l(format_plural($new, '1 new', '@count new'), "node/$node->nid", NULL, NULL, 'new'); } } $rows[] = array( check_plain(node_get_types('name', $node->type)), l($node->title, "node/$node->nid") .' '. theme('mark', node_mark($node->nid, $node->changed)), theme('username', $node), array('class' => 'replies', 'data' => $comments), t('!time ago', array('!time' => format_interval(time() - $node->last_updated))) ); } if (!$rows) { $rows[] = array(array('data' => t('No posts available.'), 'colspan' => '5')); } $header = array(t('Type'), t('Post'), t('Author'), t('Replies'), t('Last updated')); $output = '
'; $output .= theme('table', $header, $rows); $output .= theme('pager', NULL, 25, 0); $output .= '
'; return $output; } loki_website/modules/tracker/tracker.css0000644000004100000410000000021610470021352021027 0ustar www-datawww-data/* $Id: tracker.css,v 1.1 2006/08/14 07:14:50 drumm Exp $ */ #tracker td.replies { text-align: center; } #tracker table { width: 100%; } loki_website/modules/legacy/0000755000004100000410000000000010744012100016467 5ustar www-datawww-dataloki_website/modules/legacy/legacy.module0000644000004100000410000001714610715222446021171 0ustar www-datawww-data'. t('The legacy module provides legacy handlers for upgrades from older installations. These handlers help automatically redirect references to pages from old installations and prevent page not found errors for your site.') .'

'; $output .= '

'. t('The legacy module handles legacy style taxonomy page, taxonomy feed, and blog feed paths. It also handles URL upgrades from Drupal 4.1. It rewrites old-style URLs to new-style URLs (clean URLs). ') .'

'; $output .= t('

Example Mappings:

'); $output .= '

'. t('For more information please read the configuration and customization handbook Legacy page.', array('@legacy' => 'http://drupal.org/handbook/modules/legacy/')) .'

'; return $output; } } /** * Implementation of hook_menu(). * * Registers menu paths used in earlier Drupal versions. */ function legacy_menu($may_cache) { $items = array(); if ($may_cache) { // Map "taxonomy/page/or/52,97" to "taxonomy/term/52+97". $items[] = array('path' => 'taxonomy/page', 'title' => t('Taxonomy'), 'callback' => 'legacy_taxonomy_page', 'access' => TRUE, 'type' => MENU_CALLBACK); // Map "taxonomy/feed/or/52,97" to "taxonomy/term/52+97/0/feed". $items[] = array('path' => 'taxonomy/feed', 'title' => t('Taxonomy'), 'callback' => 'legacy_taxonomy_feed', 'access' => TRUE, 'type' => MENU_CALLBACK); // Map "blog/feed/52" to "blog/52/feed". $items[] = array('path' => 'blog/feed', 'title' => t('Blog'), 'callback' => 'legacy_blog_feed', 'access' => TRUE, 'type' => MENU_CALLBACK); } else { // Map "node/view/52" to "node/52". $items[] = array('path' => 'node/view', 'title' => t('View'), 'callback' => 'drupal_goto', 'callback arguments' => array('node/'. arg(2), NULL, NULL, 301), 'access' => TRUE, 'type' => MENU_CALLBACK); // Map "book/view/52" to "node/52". $items[] = array('path' => 'book/view', 'title' => t('View'), 'callback' => 'drupal_goto', 'callback arguments' => array('node/'. arg(2), NULL, NULL, 301), 'access' => TRUE, 'type' => MENU_CALLBACK); // Map "user/view/52" to "user/52". $items[] = array('path' => 'user/view', 'title' => t('View'), 'callback' => 'drupal_goto', 'callback arguments' => array('user/'. arg(2), NULL, NULL, 301), 'access' => TRUE, 'type' => MENU_CALLBACK); } return $items; } /** * Menu callback; redirects users to new taxonomy page paths. */ function legacy_taxonomy_page($operation = 'or', $str_tids = '') { if ($operation == 'or') { $str_tids = str_replace(',', '+', $str_tids); } drupal_goto('taxonomy/term/'. $str_tids); } /** * Menu callback; redirects users to new taxonomy feed paths. */ function legacy_taxonomy_feed($operation = 'or', $str_tids = '') { if ($operation == 'or') { $str_tids = str_replace(',', '+', $str_tids); } drupal_goto('taxonomy/term/'. $str_tids .'/0/feed'); } /** * Menu callback; redirects users to new blog feed paths. */ function legacy_blog_feed($str_uid = '') { // if URL is of form blog/feed/52 redirect // if URL is of form blog/feed we have to call blog_feed_last(). if (is_numeric($str_uid)) { drupal_goto('blog/'. $str_uid .'/feed'); } else { module_invoke('blog', 'feed_last'); } } /** * Implementation of hook_filter(). Handles URL upgrades from Drupal 4.1. */ function legacy_filter($op, $delta = 0, $format = -1, $text = '') { switch ($op) { case 'list': return array(t('Legacy filter')); case 'description': return t('Replaces URLs from Drupal 4.1 (and lower) with updated equivalents.'); case 'process': return _legacy_filter_old_urls($text); case 'settings': return; default: return $text; } } /** * Rewrite legacy URLs. * * This is a *temporary* filter to rewrite old-style URLs to new-style * URLs (clean URLs). Currently, URLs are being rewritten dynamically * (ie. "on output"), however when these rewrite rules have been tested * enough, we will use them to permanently rewrite the links in node * and comment bodies. */ function _legacy_filter_old_urls($text) { if (!variable_get('rewrite_old_urls', 0)) { return $text; } global $base_url; $end = substr($base_url, 12); if (variable_get('clean_url', '0') == '0') { // Relative URLs: // rewrite 'node.php?id=[&cid=]' style URLs: $text = eregi_replace("\"(node)\.php\?id=([[:digit:]]+)(&cid=)?([[:digit:]]*)", "\"?q=\\1/view/\\2/\\4", $text); // rewrite 'module.php?mod={&=}' style URLs: $text = ereg_replace("\"module\.php\?(&?[[:alpha:]]+=([[:alnum:]]+))(&?[[:alpha:]]+=([[:alnum:]]+))(&?[[:alpha:]]+=([[:alnum:]]+))", "\"?q=\\2/\\4/\\6" , $text); $text = ereg_replace("\"module\.php\?(&?[[:alpha:]]+=([[:alnum:]]+))(&?[[:alpha:]]+=([[:alnum:]]+))", "\"?q=\\2/\\4", $text); $text = ereg_replace("\"module\.php\?(&?[[:alpha:]]+=([[:alnum:]]+))", "\"?q=\\2", $text); // Absolute URLs: // rewrite 'node.php?id=[&cid=]' style URLs: $text = eregi_replace("$end/(node)\.php\?id=([[:digit:]]+)(&cid=)?([[:digit:]]*)", "$end/?q=\\1/view/\\2/\\4", $text); // rewrite 'module.php?mod={&=}' style URLs: $text = ereg_replace("$end/module\.php\?(&?[[:alpha:]]+=([[:alnum:]]+))(&?[[:alpha:]]+=([[:alnum:]]+))(&?[[:alpha:]]+=([[:alnum:]]+))", "$end/?q=\\2/\\4/\\6" , $text); $text = ereg_replace("$end/module\.php\?(&?[[:alpha:]]+=([[:alnum:]]+))(&?[[:alpha:]]+=([[:alnum:]]+))", "$end/?q=\\2/\\4", $text); $text = ereg_replace("$end/module\.php\?(&?[[:alpha:]]+=([[:alnum:]]+))", "\"$end/?q=\\2", $text); } else { // Relative URLs: // Rewrite 'node.php?id=[&cid=]' style URLs: $text = eregi_replace("\"(node)\.php\?id=([[:digit:]]+)(&cid=)?([[:digit:]]*)", "\"\\1/view/\\2/\\4", $text); // Rewrite 'module.php?mod={&=}' style URLs: $text = ereg_replace("\"module\.php\?(&?[[:alpha:]]+=([[:alnum:]]+))(&?[[:alpha:]]+=([[:alnum:]]+))(&?[[:alpha:]]+=([[:alnum:]]+))", "\"\\2/\\4/\\6", $text); $text = ereg_replace("\"module\.php\?(&?[[:alpha:]]+=([[:alnum:]]+))(&?[[:alpha:]]+=([[:alnum:]]+))", "\"\\2/\\4", $text); $text = ereg_replace("\"module\.php\?(&?[[:alpha:]]+=([[:alnum:]]+))", "\"\\2", $text); // Absolute URLs: // Rewrite 'node.php?id=[&cid=]' style URLs: $text = eregi_replace("$end/(node)\.php\?id=([[:digit:]]+)(&cid=)?([[:digit:]]*)", "$end/\\1/view/\\2/\\4", $text); // Rewrite 'module.php?mod={&=}' style URLs: $text = ereg_replace("$end/module\.php\?(&?[[:alpha:]]+=([[:alnum:]]+))(&?[[:alpha:]]+=([[:alnum:]]+))(&?[[:alpha:]]+=([[:alnum:]]+))", "$end/\\2/\\4/\\6", $text); $text = ereg_replace("$end/module\.php\?(&?[[:alpha:]]+=([[:alnum:]]+))(&?[[:alpha:]]+=([[:alnum:]]+))", "$end/\\2/\\4", $text); $text = ereg_replace("$end/module\.php\?(&?[[:alpha:]]+=([[:alnum:]]+))", "$end/\\2", $text); } return $text; } loki_website/modules/legacy/legacy.info0000644000004100000410000000050710741515024020624 0ustar www-datawww-data; $Id: legacy.info,v 1.3 2006/11/21 20:55:34 dries Exp $ name = Legacy description = Provides legacy handlers for upgrades from older Drupal installations. package = Core - optional version = VERSION ; Information added by drupal.org packaging script on 2008-01-10 version = "5.6" project = "drupal" datestamp = "1200003604" loki_website/modules/upload/0000755000004100000410000000000010744012100016507 5ustar www-datawww-dataloki_website/modules/upload/upload.info0000644000004100000410000000046410741515024020666 0ustar www-datawww-data; $Id: upload.info,v 1.3 2006/11/21 20:55:35 dries Exp $ name = Upload description = Allows users to upload and attach files to content. package = Core - optional version = VERSION ; Information added by drupal.org packaging script on 2008-01-10 version = "5.6" project = "drupal" datestamp = "1200003604" loki_website/modules/upload/upload.module0000644000004100000410000010346410740274650021232 0ustar www-datawww-data'. t('The upload module allows users to upload files to the site. The ability to upload files to a site is important for members of a community who want to share work. It is also useful to administrators who want to keep uploaded files connected to a node or page.') .'

'; $output .= '

'. t('Users with the upload files permission can upload attachments. You can choose which post types can take attachments on the content types settings page. Each user role can be customized for the file size of uploads, and the dimension of image files.') .'

'; $output .= '

'. t('For more information please read the configuration and customization handbook Upload page.', array('@upload' => 'http://drupal.org/handbook/modules/upload/')) .'

'; return $output; case 'admin/settings/upload': return '

'. t('Users with the upload files permission can upload attachments. Users with the view uploaded files permission can view uploaded attachments. You can choose which post types can take attachments on the content types settings page.', array('@permissions' => url('admin/user/access'), '@types' => url('admin/settings/types'))) .'

'; } } /** * Implementation of hook_perm(). */ function upload_perm() { return array('upload files', 'view uploaded files'); } /** * Implementation of hook_link(). */ function upload_link($type, $node = NULL, $teaser = FALSE) { $links = array(); // Display a link with the number of attachments if ($teaser && $type == 'node' && isset($node->files) && user_access('view uploaded files')) { $num_files = 0; foreach ($node->files as $file) { if ($file->list) { $num_files++; } } if ($num_files) { $links['upload_attachments'] = array( 'title' => format_plural($num_files, '1 attachment', '@count attachments'), 'href' => "node/$node->nid", 'attributes' => array('title' => t('Read full article to view attachments.')), 'fragment' => 'attachments' ); } } return $links; } /** * Implementation of hook_menu(). */ function upload_menu($may_cache) { $items = array(); if ($may_cache) { $items[] = array( 'path' => 'upload/js', 'callback' => 'upload_js', 'access' => user_access('upload files'), 'type' => MENU_CALLBACK ); $items[] = array('path' => 'admin/settings/uploads', 'title' => t('File uploads'), 'description' => t('Control how files may be attached to content.'), 'callback' => 'drupal_get_form', 'callback arguments' => array('upload_admin_settings'), 'access' => user_access('administer site configuration'), 'type' => MENU_NORMAL_ITEM); } else { // Add handlers for previewing new uploads. if (isset($_SESSION['file_previews'])) { foreach ($_SESSION['file_previews'] as $fid => $file) { $filename = file_create_filename($file->filename, file_create_path()); if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PRIVATE) { // strip file_directory_path() from filename. @see file_create_url if (strpos($filename, file_directory_path()) !== FALSE) { $filename = trim(substr($filename, strlen(file_directory_path())), '\\/'); } $filename = 'system/files/' . $filename; } $items[] = array( 'path' => $filename, 'title' => t('File download'), 'callback' => 'upload_download', 'access' => user_access('view uploaded files'), 'type' => MENU_CALLBACK ); $_SESSION['file_previews'][$fid]->_filename = $filename; } } } return $items; } /** * Form API callback to validate the upload settings form. */ function upload_admin_settings_validate($form_id, $form_values) { if (($form_values['upload_max_resolution'] != '0')) { if (!preg_match('/^[0-9]+x[0-9]+$/', $form_values['upload_max_resolution'])) { form_set_error('upload_max_resolution', t('The maximum allowed image size expressed as WIDTHxHEIGHT (e.g. 640x480). Set to 0 for no restriction.')); } } $default_uploadsize = $form_values['upload_uploadsize_default']; $default_usersize = $form_values['upload_usersize_default']; $exceed_max_msg = t('Your PHP settings limit the maximum file size per upload to %size.', array('%size' => format_size(file_upload_max_size()))).'
'; $more_info = t("Depending on your sever environment, these settings may be changed in the system-wide php.ini file, a php.ini file in your Drupal root directory, in your Drupal site's settings.php file, or in the .htaccess file in your Drupal root directory."); if (!is_numeric($default_uploadsize) || ($default_uploadsize <= 0)) { form_set_error('upload_uploadsize_default', t('The %role file size limit must be a number and greater than zero.', array('%role' => t('default')))); } if (!is_numeric($default_usersize) || ($default_usersize <= 0)) { form_set_error('upload_usersize_default', t('The %role file size limit must be a number and greater than zero.', array('%role' => t('default')))); } if ($default_uploadsize * 1024 * 1024 > file_upload_max_size()) { form_set_error('upload_uploadsize_default', $exceed_max_msg . $more_info); $more_info = ''; } if ($default_uploadsize > $default_usersize) { form_set_error('upload_uploadsize_default', t('The %role maximum file size per upload is greater than the total file size allowed per user', array('%role' => t('default')))); } foreach ($form_values['roles'] as $rid => $role) { $uploadsize = $form_values['upload_uploadsize_'. $rid]; $usersize = $form_values['upload_usersize_'. $rid]; if (!is_numeric($uploadsize) || ($uploadsize <= 0)) { form_set_error('upload_uploadsize_'. $rid, t('The %role file size limit must be a number and greater than zero.', array('%role' => $role))); } if (!is_numeric($usersize) || ($usersize <= 0)) { form_set_error('upload_usersize_'. $rid, t('The %role file size limit must be a number and greater than zero.', array('%role' => $role))); } if ($uploadsize * 1024 * 1024 > file_upload_max_size()) { form_set_error('upload_uploadsize_'. $rid, $exceed_max_msg . $more_info); $more_info = ''; } if ($uploadsize > $usersize) { form_set_error('upload_uploadsize_'. $rid, t('The %role maximum file size per upload is greater than the total file size allowed per user', array('%role' => $role))); } } } /** * Menu callback for the upload settings form. */ function upload_admin_settings() { $upload_extensions_default = variable_get('upload_extensions_default', 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'); $upload_uploadsize_default = variable_get('upload_uploadsize_default', 1); $upload_usersize_default = variable_get('upload_usersize_default', 1); $form['settings_general'] = array( '#type' => 'fieldset', '#title' => t('General settings'), '#collapsible' => TRUE, ); $form['settings_general']['upload_max_resolution'] = array( '#type' => 'textfield', '#title' => t('Maximum resolution for uploaded images'), '#default_value' => variable_get('upload_max_resolution', 0), '#size' => 15, '#maxlength' => 10, '#description' => t('The maximum allowed image size (e.g. 640x480). Set to 0 for no restriction.'), '#field_suffix' => ''. t('WIDTHxHEIGHT') .'' ); $form['settings_general']['upload_list_default'] = array( '#type' => 'select', '#title' => t('List files by default'), '#default_value' => variable_get('upload_list_default', 1), '#options' => array(0 => t('No'), 1 => t('Yes')), '#description' => t('Set whether files attached to nodes are listed or not in the node view by default.'), ); $form['settings_general']['upload_extensions_default'] = array( '#type' => 'textfield', '#title' => t('Default permitted file extensions'), '#default_value' => $upload_extensions_default, '#maxlength' => 255, '#description' => t('Default extensions that users can upload. Separate extensions with a space and do not include the leading dot.'), ); $form['settings_general']['upload_uploadsize_default'] = array( '#type' => 'textfield', '#title' => t('Default maximum file size per upload'), '#default_value' => $upload_uploadsize_default, '#size' => 5, '#maxlength' => 5, '#description' => t('The default maximum file size a user can upload.'), '#field_suffix' => t('MB') ); $form['settings_general']['upload_usersize_default'] = array( '#type' => 'textfield', '#title' => t('Default total file size per user'), '#default_value' => $upload_usersize_default, '#size' => 5, '#maxlength' => 5, '#description' => t('The default maximum size of all files a user can have on the site.'), '#field_suffix' => t('MB') ); $form['settings_general']['upload_max_size'] = array('#value' => '

'. t('Your PHP settings limit the maximum file size per upload to %size.', array('%size' => format_size(file_upload_max_size()))).'

'); $roles = user_roles(0, 'upload files'); $form['roles'] = array('#type' => 'value', '#value' => $roles); foreach ($roles as $rid => $role) { $form['settings_role_'. $rid] = array( '#type' => 'fieldset', '#title' => t('Settings for @role', array('@role' => $role)), '#collapsible' => TRUE, '#collapsed' => TRUE, ); $form['settings_role_'. $rid]['upload_extensions_'. $rid] = array( '#type' => 'textfield', '#title' => t('Permitted file extensions'), '#default_value' => variable_get('upload_extensions_'. $rid, $upload_extensions_default), '#maxlength' => 255, '#description' => t('Extensions that users in this role can upload. Separate extensions with a space and do not include the leading dot.'), ); $form['settings_role_'. $rid]['upload_uploadsize_'. $rid] = array( '#type' => 'textfield', '#title' => t('Maximum file size per upload'), '#default_value' => variable_get('upload_uploadsize_'. $rid, $upload_uploadsize_default), '#size' => 5, '#maxlength' => 5, '#description' => t('The maximum size of a file a user can upload (in megabytes).'), ); $form['settings_role_'. $rid]['upload_usersize_'. $rid] = array( '#type' => 'textfield', '#title' => t('Total file size per user'), '#default_value' => variable_get('upload_usersize_'. $rid, $upload_usersize_default), '#size' => 5, '#maxlength' => 5, '#description' => t('The maximum size of all files a user can have on the site (in megabytes).'), ); } return system_settings_form($form); } function upload_download() { foreach ($_SESSION['file_previews'] as $file) { if ($file->_filename == $_GET['q']) { file_transfer($file->filepath, array('Content-Type: '. mime_header_encode($file->filemime), 'Content-Length: '. $file->filesize)); } } } function upload_file_download($file) { $file = file_create_path($file); $result = db_query("SELECT f.* FROM {files} f WHERE filepath = '%s'", $file); if ($file = db_fetch_object($result)) { if (user_access('view uploaded files')) { $node = node_load($file->nid); if (node_access('view', $node)) { $type = mime_header_encode($file->filemime); return array( 'Content-Type: '. $type, 'Content-Length: '. $file->filesize, ); } else { return -1; } } else { return -1; } } } /** * Save new uploads and attach them to the node object. * append file_previews to the node object as well. */ function _upload_prepare(&$node) { // Clean up old file previews if a post didn't get the user to this page. // i.e. the user left the edit page, because they didn't want to upload anything. if(count($_POST) == 0) { if (is_array($_SESSION['file_previews']) && count($_SESSION['file_previews'])) { foreach ($_SESSION['file_previews'] as $fid => $file) { file_delete($file->filepath); } unset($_SESSION['file_previews']); } } // $_SESSION['file_current_upload'] tracks the fid of the file submitted this page request. // form_builder sets the value of file->list to 0 for checkboxes added to a form after // it has been submitted. Since unchecked checkboxes have no return value and do not // get a key in _POST form_builder has no way of knowing the difference between a check // box that wasn't present on the last form build, and a checkbox that is unchecked. unset($_SESSION['file_current_upload']); global $user; // Save new file uploads to tmp dir. if (($file = file_check_upload()) && user_access('upload files')) { // Scale image uploads. $file = _upload_image($file); $key = 'upload_'. count($_SESSION['file_previews']); $file->fid = $key; $file->source = $key; $file->list = variable_get('upload_list_default',1); $_SESSION['file_previews'][$key] = $file; // Store the uploaded fid for this page request in case of submit without // preview or attach. See earlier notes. $_SESSION['file_current_upload'] = $key; } // Attach file previews to node object. if (is_array($_SESSION['file_previews']) && count($_SESSION['file_previews'])) { foreach ($_SESSION['file_previews'] as $fid => $file) { if ($user->uid != 1) { // Here something.php.pps becomes something.php_.pps $file->filename = upload_munge_filename($file->filename, NULL, 0); $file->description = $file->filename; } $node->files[$fid] = $file; } } } function upload_form_alter($form_id, &$form) { if ($form_id == 'node_type_form' && isset($form['identity']['type'])) { $form['workflow']['upload'] = array( '#type' => 'radios', '#title' => t('Attachments'), '#default_value' => variable_get('upload_'. $form['#node_type']->type, 1), '#options' => array(t('Disabled'), t('Enabled')), ); } if (isset($form['type'])) { $node = $form['#node']; if ($form['type']['#value'] .'_node_form' == $form_id && variable_get("upload_$node->type", TRUE)) { drupal_add_js('misc/progress.js'); drupal_add_js('misc/upload.js'); // Attachments fieldset $form['attachments'] = array( '#type' => 'fieldset', '#access' => user_access('upload files'), '#title' => t('File attachments'), '#collapsible' => TRUE, '#collapsed' => empty($node->files), '#description' => t('Changes made to the attachments are not permanent until you save this post. The first "listed" file will be included in RSS feeds.'), '#prefix' => '
', '#suffix' => '
', '#weight' => 30, ); // Wrapper for fieldset contents (used by upload JS). $form['attachments']['wrapper'] = array( '#prefix' => '
', '#suffix' => '
', ); // Make sure necessary directories for upload.module exist and are // writable before displaying the attachment form. $path = file_directory_path(); $temp = file_directory_temp(); // Note: pass by reference if (!file_check_directory($path, FILE_CREATE_DIRECTORY) || !file_check_directory($temp, FILE_CREATE_DIRECTORY)) { $form['attachments']['#description'] = t('File attachments are disabled. The file directories have not been properly configured.'); if (user_access('administer site configuration')) { $form['attachments']['#description'] .= ' '. t('Please visit the file system configuration page.', array('@admin-file-system' => url('admin/settings/file-system'))); } else { $form['attachments']['#description'] .= ' '. t('Please contact the site administrator.'); } } else { $form['attachments']['wrapper'] += _upload_form($node); $form['#attributes']['enctype'] = 'multipart/form-data'; } } } } function _upload_validate(&$node) { // Accumulator for disk space quotas. $filesize = 0; // Check if node->files exists, and if it contains something. if (is_array($node->files)) { // Update existing files with form data. foreach ($node->files as $fid => $file) { // Convert file to object for compatibility $file = (object)$file; // Validate new uploads. if (strpos($fid, 'upload') !== FALSE && !$file->remove) { global $user; // Bypass validation for uid = 1. if ($user->uid != 1) { // Update filesize accumulator. $filesize += $file->filesize; // Validate file against all users roles. // Only denies an upload when all roles prevent it. $total_usersize = upload_space_used($user->uid) + $filesize; $error = array(); foreach ($user->roles as $rid => $name) { $extensions = variable_get("upload_extensions_$rid", variable_get('upload_extensions_default', 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp')); $uploadsize = variable_get("upload_uploadsize_$rid", variable_get('upload_uploadsize_default', 1)) * 1024 * 1024; $usersize = variable_get("upload_usersize_$rid", variable_get('upload_usersize_default', 1)) * 1024 * 1024; $regex = '/\.('. ereg_replace(' +', '|', preg_quote($extensions)) .')$/i'; if (!preg_match($regex, $file->filename)) { $error['extension']++; } if ($uploadsize && $file->filesize > $uploadsize) { $error['uploadsize']++; } if ($usersize && $total_usersize + $file->filesize > $usersize) { $error['usersize']++; } } $user_roles = count($user->roles); $valid = TRUE; if ($error['extension'] == $user_roles) { form_set_error('upload', t('The selected file %name can not be attached to this post, because it is only possible to attach files with the following extensions: %files-allowed.', array('%name' => $file->filename, '%files-allowed' => $extensions))); $valid = FALSE; } elseif ($error['uploadsize'] == $user_roles) { form_set_error('upload', t('The selected file %name can not be attached to this post, because it exceeded the maximum filesize of %maxsize.', array('%name' => $file->filename, '%maxsize' => format_size($uploadsize)))); $valid = FALSE; } elseif ($error['usersize'] == $user_roles) { form_set_error('upload', t('The selected file %name can not be attached to this post, because the disk quota of %quota has been reached.', array('%name' => $file->filename, '%quota' => format_size($usersize)))); $valid = FALSE; } elseif (strlen($file->filename) > 255) { form_set_error('upload', t('The selected file %name can not be attached to this post, because the filename is too long.', array('%name' => $file->filename))); $valid = FALSE; } if (!$valid) { unset($node->files[$fid], $_SESSION['file_previews'][$fid]); file_delete($file->filepath); } } } } } } /** * Implementation of hook_nodeapi(). */ function upload_nodeapi(&$node, $op, $teaser) { switch ($op) { case 'load': $output = ''; if (variable_get("upload_$node->type", 1) == 1) { $output['files'] = upload_load($node); return $output; } break; case 'prepare': _upload_prepare($node); break; case 'validate': _upload_validate($node); break; case 'view': if (isset($node->files) && user_access('view uploaded files')) { // Add the attachments list to node body with a heavy // weight to ensure they're below other elements if (count($node->files)) { if (!$teaser && user_access('view uploaded files')) { $node->content['files'] = array( '#value' => theme('upload_attachments', $node->files), '#weight' => 50, ); } } } break; case 'alter': if (isset($node->files) && user_access('view uploaded files')) { // Manipulate so that inline references work in preview if (!variable_get('clean_url', 0)) { $previews = array(); foreach ($node->files as $file) { if (strpos($file->fid, 'upload') !== FALSE) { $previews[] = $file; } } // URLs to files being previewed are actually Drupal paths. When Clean // URLs are disabled, the two do not match. We perform an automatic // replacement from temporary to permanent URLs. That way, the author // can use the final URL in the body before having actually saved (to // place inline images for example). foreach ($previews as $file) { $old = file_create_filename($file->filename, file_create_path()); $new = url($old); $node->body = str_replace($old, $new, $node->body); $node->teaser = str_replace($old, $new, $node->teaser); } } } break; case 'insert': case 'update': if (user_access('upload files')) { upload_save($node); } break; case 'delete': upload_delete($node); break; case 'delete revision': upload_delete_revision($node); break; case 'search result': return is_array($node->files) ? format_plural(count($node->files), '1 attachment', '@count attachments') : NULL; case 'rss item': if (is_array($node->files)) { $files = array(); foreach ($node->files as $file) { if ($file->list) { $files[] = $file; } } if (count($files) > 0) { // RSS only allows one enclosure per item $file = array_shift($files); return array( array( 'key' => 'enclosure', 'attributes' => array( 'url' => file_create_url($file->filepath), 'length' => $file->filesize, 'type' => $file->filemime ) ) ); } } return array(); } } /** * Displays file attachments in table */ function theme_upload_attachments($files) { $header = array(t('Attachment'), t('Size')); $rows = array(); foreach ($files as $file) { $file = (object)$file; if ($file->list && !$file->remove) { // Generate valid URL for both existing attachments and preview of new attachments (these have 'upload' in fid) $href = file_create_url((strpos($file->fid, 'upload') === FALSE ? $file->filepath : file_create_filename($file->filename, file_create_path()))); $text = $file->description ? $file->description : $file->filename; $rows[] = array(l($text, $href), format_size($file->filesize)); } } if (count($rows)) { return theme('table', $header, $rows, array('id' => 'attachments')); } } /** * Determine how much disk space is occupied by a user's uploaded files. * * @param $uid * The integer user id of a user. * @return * The amount of disk space used by the user in bytes. */ function upload_space_used($uid) { return db_result(db_query('SELECT SUM(filesize) FROM {files} f INNER JOIN {node} n ON f.nid = n.nid WHERE n.uid = %d', $uid)); } /** * Determine how much disk space is occupied by uploaded files. * * @return * The amount of disk space used by uploaded files in bytes. */ function upload_total_space_used() { return db_result(db_query('SELECT SUM(filesize) FROM {files}')); } /** * Munge the filename as needed for security purposes. * * @param $filename * The name of a file to modify. * @param $extensions * A space separated list of valid extensions. If this is blank, we'll use * the admin-defined defaults for the user role from upload_extensions_$rid. * @param $alerts * Whether alerts (watchdog, drupal_set_message()) should be displayed. * @return $filename * The potentially modified $filename. */ function upload_munge_filename($filename, $extensions = NULL, $alerts = 1) { global $user; $original = $filename; // Allow potentially insecure uploads for very savvy users and admin if (!variable_get('allow_insecure_uploads', 0)) { if (!isset($extensions)) { $extensions = ''; foreach ($user->roles as $rid => $name) { $extensions .= ' '. variable_get("upload_extensions_$rid", variable_get('upload_extensions_default', 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp')); } } $whitelist = array_unique(explode(' ', trim($extensions))); $filename_parts = explode('.', $filename); $new_filename = array_shift($filename_parts); // Remove file basename. $final_extension = array_pop($filename_parts); // Remove final extension. foreach ($filename_parts as $filename_part) { $new_filename .= ".$filename_part"; if (!in_array($filename_part, $whitelist) && preg_match("/^[a-zA-Z]{2,5}\d?$/", $filename_part)) { $new_filename .= '_'; } } $filename = "$new_filename.$final_extension"; } if ($alerts && $original != $filename) { $message = t('Your filename has been renamed to conform to site policy.'); drupal_set_message($message); } return $filename; } /** * Undo the effect of upload_munge_filename(). */ function upload_unmunge_filename($filename) { return str_replace('_.', '.', $filename); } function upload_save(&$node) { if (!is_array($node->files)) { return; } foreach ($node->files as $fid => $file) { // Convert file to object for compatibility $file = (object)$file; // Remove file. Process removals first since no further processing // will be required. if ($file->remove) { // Remove file previews... if (strpos($file->fid, 'upload') !== FALSE) { file_delete($file->filepath); } // Remove managed files. else { db_query('DELETE FROM {file_revisions} WHERE fid = %d AND vid = %d', $fid, $node->vid); // Only delete a file if it isn't used by any revision $count = db_result(db_query('SELECT COUNT(fid) FROM {file_revisions} WHERE fid = %d', $fid)); if ($count < 1) { db_query('DELETE FROM {files} WHERE fid = %d', $fid); file_delete($file->filepath); } } } // New file upload elseif (strpos($file->fid, 'upload') !== FALSE) { if ($file = file_save_upload($file, $file->filename)) { $file->fid = db_next_id('{files}_fid'); db_query("INSERT INTO {files} (fid, nid, filename, filepath, filemime, filesize) VALUES (%d, %d, '%s', '%s', '%s', %d)", $file->fid, $node->nid, $file->filename, $file->filepath, $file->filemime, $file->filesize); db_query("INSERT INTO {file_revisions} (fid, vid, list, description) VALUES (%d, %d, %d, '%s')", $file->fid, $node->vid, $file->list, $file->description); // Tell other modules where the file was stored. $node->files[$fid] = $file; } unset($_SESSION['file_previews'][$fid]); } // Create a new revision, as needed elseif ($node->old_vid && is_numeric($fid)) { db_query("INSERT INTO {file_revisions} (fid, vid, list, description) VALUES (%d, %d, %d, '%s')", $file->fid, $node->vid, $file->list, $file->description); } // Update existing revision else { db_query("UPDATE {file_revisions} SET list = %d, description = '%s' WHERE fid = %d AND vid = %d", $file->list, $file->description, $file->fid, $node->vid); } } } function upload_delete($node) { $files = array(); $result = db_query('SELECT * FROM {files} WHERE nid = %d', $node->nid); while ($file = db_fetch_object($result)) { $files[$file->fid] = $file; } foreach ($files as $fid => $file) { // Delete all file revision information associated with the node db_query('DELETE FROM {file_revisions} WHERE fid = %d', $fid); file_delete($file->filepath); } // Delete all files associated with the node db_query('DELETE FROM {files} WHERE nid = %d', $node->nid); } function upload_delete_revision($node) { if (is_array($node->files)) { foreach ($node->files as $file) { // Check if the file will be used after this revision is deleted $count = db_result(db_query('SELECT COUNT(fid) FROM {file_revisions} WHERE fid = %d', $file->fid)); // if the file won't be used, delete it if ($count < 2) { db_query('DELETE FROM {files} WHERE fid = %d', $file->fid); file_delete($file->filepath); } } } // delete the revision db_query('DELETE FROM {file_revisions} WHERE vid = %d', $node->vid); } function _upload_form($node) { $form['#theme'] = 'upload_form_new'; if (is_array($node->files) && count($node->files)) { $form['files']['#theme'] = 'upload_form_current'; $form['files']['#tree'] = TRUE; foreach ($node->files as $key => $file) { // Generate valid URL for both existing attachments and preview of new attachments (these have 'upload' in fid) $description = file_create_url((strpos($file->fid, 'upload') === FALSE ? $file->filepath : file_create_filename($file->filename, file_create_path()))); $description = "". check_plain($description) .""; $form['files'][$key]['description'] = array('#type' => 'textfield', '#default_value' => (strlen($file->description)) ? $file->description : $file->filename, '#maxlength' => 256, '#description' => $description ); $form['files'][$key]['size'] = array('#value' => format_size($file->filesize)); $form['files'][$key]['remove'] = array('#type' => 'checkbox', '#default_value' => $file->remove); $form['files'][$key]['list'] = array('#type' => 'checkbox', '#default_value' => $file->list); // if the file was uploaded this page request, set value. this fixes the problem // formapi has recognizing new checkboxes. see comments in _upload_prepare. if ($_SESSION['file_current_upload'] == $file->fid) { $form['files'][$key]['list']['#value'] = variable_get('upload_list_default',1); } $form['files'][$key]['filename'] = array('#type' => 'value', '#value' => $file->filename); $form['files'][$key]['filepath'] = array('#type' => 'value', '#value' => $file->filepath); $form['files'][$key]['filemime'] = array('#type' => 'value', '#value' => $file->filemime); $form['files'][$key]['filesize'] = array('#type' => 'value', '#value' => $file->filesize); $form['files'][$key]['fid'] = array('#type' => 'value', '#value' => $file->fid); } } if (user_access('upload files')) { // This div is hidden when the user uploads through JS. $form['new'] = array( '#prefix' => '
', '#suffix' => '
', ); $form['new']['upload'] = array('#type' => 'file', '#title' => t('Attach new file'), '#size' => 40); $form['new']['attach'] = array('#type' => 'button', '#value' => t('Attach'), '#name' => 'attach', '#id' => 'attach-button'); // The class triggers the js upload behaviour. $form['attach-url'] = array('#type' => 'hidden', '#value' => url('upload/js', NULL, NULL, TRUE), '#attributes' => array('class' => 'upload')); } // Needed for JS $form['current']['vid'] = array('#type' => 'hidden', '#value' => $node->vid); return $form; } /** * Theme the attachments list. */ function theme_upload_form_current(&$form) { $header = array(t('Delete'), t('List'), t('Description'), t('Size')); foreach (element_children($form) as $key) { $row = array(); $row[] = drupal_render($form[$key]['remove']); $row[] = drupal_render($form[$key]['list']); $row[] = drupal_render($form[$key]['description']); $row[] = drupal_render($form[$key]['size']); $rows[] = $row; } $output = theme('table', $header, $rows); $output .= drupal_render($form); return $output; } /** * Theme the attachment form. * Note: required to output prefix/suffix. */ function theme_upload_form_new($form) { $output = drupal_render($form); return $output; } function upload_load($node) { $files = array(); if ($node->vid) { $result = db_query('SELECT * FROM {files} f INNER JOIN {file_revisions} r ON f.fid = r.fid WHERE r.vid = %d ORDER BY f.fid', $node->vid); while ($file = db_fetch_object($result)) { $files[$file->fid] = $file; } } return $files; } /** * Check an upload, if it is an image, make sure it fits within the * maximum dimensions allowed. */ function _upload_image($file) { $info = image_get_info($file->filepath); if ($info) { list($width, $height) = explode('x', variable_get('upload_max_resolution', 0)); if ($width && $height) { $result = image_scale($file->filepath, $file->filepath, $width, $height); if ($result) { $file->filesize = filesize($file->filepath); drupal_set_message(t('The image was resized to fit within the maximum allowed resolution of %resolution pixels.', array('%resolution' => variable_get('upload_max_resolution', 0)))); } } } return $file; } /** * Menu-callback for JavaScript-based uploads. */ function upload_js() { // We only do the upload.module part of the node validation process. $node = (object)$_POST; // Load existing node files. $node->files = upload_load($node); // Handle new uploads, and merge tmp files into node-files. _upload_prepare($node); _upload_validate($node); $form = _upload_form($node); foreach (module_implements('form_alter') as $module) { $function = $module .'_form_alter'; $function('upload_js', $form); } $form = form_builder('upload_js', $form); $output = theme('status_messages') . drupal_render($form); // We send the updated file attachments form. print drupal_to_js(array('status' => TRUE, 'data' => $output)); exit; } loki_website/modules/color/0000755000004100000410000000000010744012100016341 5ustar www-datawww-dataloki_website/modules/color/images/0000755000004100000410000000000010744012100017606 5ustar www-datawww-dataloki_website/modules/color/images/lock.png0000644000004100000410000000037310521124762021262 0ustar www-datawww-dataPNG  IHDR 26tIME +a'PLTEaaaxvg-)=tRNS@foIDAT( @̚YI~DeB[FUILn#&/e*- VYZ_e\QoE}G{;IENDB`loki_website/modules/color/images/hook.png0000644000004100000410000000021410521124762021264 0ustar www-datawww-dataPNG  IHDRQPtIME %Ù,PLTEDDDtRNS@f!IDAT(c` 0BO\{yjyy2P5,hIENDB`loki_website/modules/color/color.install0000644000004100000410000000245210535204176021070 0ustar www-datawww-data $info['GD Version'], ); // Check PNG support if (function_exists('imagecreatefrompng')) { $requirements['gd']['severity'] = REQUIREMENT_OK; } else { $requirements['gd']['severity'] = REQUIREMENT_ERROR; $requirements['gd']['description'] = t('The GD library for PHP is enabled, but was compiled without PNG support. Please check the PHP image documentation for information on how to correct this.', array('@url' => 'http://www.php.net/manual/en/ref.image.php')); } } else { $requirements['gd'] = array( 'value' => t('Not installed'), 'severity' => REQUIREMENT_ERROR, 'description' => t('The GD library for PHP is missing or outdated. Please check the PHP image documentation for information on how to correct this.', array('@url' => 'http://www.php.net/manual/en/ref.image.php')), ); } $requirements['gd']['title'] = t('GD library'); } return $requirements; } loki_website/modules/color/color.module0000644000004100000410000004662410715745240020722 0ustar www-datawww-data'. t('Color module allows a site administrator to quickly and easily change the color scheme of the entire site. In order for color module to work however, a theme must be specifically designed to use the color changing features. The default theme, Garland, (as well as its fixed width counterpart, Minnelli) was designed to take advantage of these features. With color module, you can easily change the color of links, backgrounds, text, and more depending on which color module enabled theme you are using. Color module requires your file download method to be set to public.', array('@url' => url('admin/settings/file-system'))) .'

'; $output .= '

'. t("It is important to remember that color module saves a modified copy of the theme's style.css file in the files directory, and includes it after the theme's original style.css. This means that if you make any manual changes to your theme's style.css file, you must save your color settings again, even if they haven't changed. This causes the color module generated version of style.css in the files directory to be recreated using the new version of the original file.") .'

'; return $output; } } /** * Implementation of hook_form_alter(). */ function color_form_alter($form_id, &$form) { // Insert the color changer into the theme settings page. // TODO: Last condition in the following if disables color changer when private files are used this should be solved in a different way. See issue #92059. if ($form_id == 'system_theme_settings' && color_get_info(arg(4)) && function_exists('gd_info') && variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC) { $form['color'] = array( '#type' => 'fieldset', '#title' => t('Color scheme'), '#weight' => -1, '#attributes' => array('id' => 'color_scheme_form'), '#theme' => 'color_scheme_form', ); $form['color'] += color_scheme_form(arg(4)); $form['#submit']['color_scheme_form_submit'] = array(); } // Use the generated screenshot in the theme list if ($form_id == 'system_theme_select_form' || $form_id == 'system_themes') { $themes = list_themes(); foreach (element_children($form) as $theme) { if ($screenshot = variable_get('color_'. $theme .'_screenshot', NULL)) { if (isset($form[$theme]['screenshot'])) { $form[$theme]['screenshot']['#value'] = theme('image', $screenshot, '', '', array('class' => 'screenshot'), FALSE); } } } } } /** * Callback for the theme to alter the resources used. */ function _color_page_alter(&$vars) { global $theme_key; // Override stylesheet $path = variable_get('color_'. $theme_key .'_stylesheet', NULL); if ($path) { $vars['css']['all']['theme'][$path] = TRUE; $vars['styles'] = drupal_get_css($vars['css']); } // Override logo $logo = variable_get('color_'. $theme_key .'_logo', NULL); if ($logo && $vars['logo'] && preg_match('!'. $theme_key .'/logo.png$!', $vars['logo'])) { $vars['logo'] = base_path() . $logo; } } /** * Retrieve the color.module info for a particular theme. */ function color_get_info($theme) { $path = drupal_get_path('theme', $theme); $file = $path .'/color/color.inc'; if ($path && file_exists($file)) { include $file; return $info; } } /** * Helper function to retrieve the color palette for a particular theme. */ function color_get_palette($theme, $default = false) { // Fetch and expand default palette $fields = array('base', 'link', 'top', 'bottom', 'text'); $info = color_get_info($theme); $keys = array_keys($info['schemes']); foreach (explode(',', array_shift($keys)) as $k => $scheme) { $palette[$fields[$k]] = $scheme; } // Load variable return $default ? $palette : variable_get('color_'. $theme .'_palette', $palette); } /** * Form callback. Returns the configuration form. */ function color_scheme_form($theme) { $base = drupal_get_path('module', 'color'); $info = color_get_info($theme); // Add Farbtastic color picker drupal_add_css('misc/farbtastic/farbtastic.css', 'module', 'all', FALSE); drupal_add_js('misc/farbtastic/farbtastic.js'); // Add custom CSS/JS drupal_add_css($base .'/color.css', 'module', 'all', FALSE); drupal_add_js($base .'/color.js'); drupal_add_js(array('color' => array( 'reference' => color_get_palette($theme, true) )), 'setting'); // See if we're using a predefined scheme $current = implode(',', variable_get('color_'. $theme .'_palette', array())); // Note: we use the original theme when the default scheme is chosen. $current = isset($info['schemes'][$current]) ? $current : ($current == '' ? reset($info['schemes']) : ''); // Add scheme selector $info['schemes'][''] = t('Custom'); $form['scheme'] = array( '#type' => 'select', '#title' => t('Color set'), '#options' => $info['schemes'], '#default_value' => $current, ); // Add palette fields $palette = color_get_palette($theme); $names = array( 'base' => t('Base color'), 'link' => t('Link color'), 'top' => t('Header top'), 'bottom' => t('Header bottom'), 'text' => t('Text color') ); $form['palette']['#tree'] = true; foreach ($palette as $name => $value) { $form['palette'][$name] = array( '#type' => 'textfield', '#title' => $names[$name], '#default_value' => $value, '#size' => 8, ); } $form['theme'] = array('#type' => 'value', '#value' => arg(4)); $form['info'] = array('#type' => 'value', '#value' => $info); return $form; } /** * Theme color form. */ function theme_color_scheme_form($form) { // Include stylesheet $theme = $form['theme']['#value']; $info = $form['info']['#value']; $path = drupal_get_path('theme', $theme) .'/'; drupal_add_css($path . $info['preview_css']); // Wrapper $output .= '
'; // Color schemes $output .= drupal_render($form['scheme']); // Palette $output .= '
'; foreach (element_children($form['palette']) as $name) { $output .= drupal_render($form['palette'][$name]); } $output .= '
'; // Preview $output .= drupal_render($form); $output .= '

'. t('Preview') .'

'; $output .= '

Lorem ipsum dolor

Sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

'; // Close wrapper $output .= '
'; return $output; } /** * Submit handler for color change form. */ function color_scheme_form_submit($form_id, $values) { // Get theme coloring info if (!isset($values['info'])) { return; } $theme = $values['theme']; $info = $values['info']; // Resolve palette $palette = $values['palette']; if ($values['scheme'] != '') { $scheme = explode(',', $values['scheme']); foreach ($palette as $k => $color) { $palette[$k] = array_shift($scheme); } } // Make sure enough memory is available, if PHP's memory limit is compiled in. if (function_exists('memory_get_usage')) { // Fetch source image dimensions. $source = drupal_get_path('theme', $theme) .'/'. $info['base_image']; list($width, $height) = getimagesize($source); // We need at least a copy of the source and a target buffer of the same // size (both at 32bpp). $required = $width * $height * 8; $usage = memory_get_usage(); $limit = parse_size(ini_get('memory_limit')); if ($usage + $required > $limit) { drupal_set_message(t('There is not enough memory available to PHP to change this theme\'s color scheme. You need at least %size more. Check the PHP documentation for more information.', array('%size' => format_size($usage + $required - $limit), '@url' => 'http://www.php.net/manual/en/ini.core.php#ini.sect.resource-limits')), 'error'); return; } } // Delete old files foreach (variable_get('color_'. $theme .'_files', array()) as $file) { @unlink($file); } if ($file = dirname($file)) { @rmdir($file); } // Don't render the default colorscheme, use the standard theme instead. if (implode(',', color_get_palette($theme, true)) == implode(',', $palette) || $values['op'] == t('Reset to defaults')) { variable_del('color_'. $theme .'_palette'); variable_del('color_'. $theme .'_stylesheet'); variable_del('color_'. $theme .'_logo'); variable_del('color_'. $theme .'_files'); variable_del('color_'. $theme .'_screenshot'); return; } // Prepare target locations for generated files $id = $theme .'-'. substr(md5(serialize($palette) . microtime()), 0, 8); $paths['color'] = file_directory_path() .'/color'; $paths['target'] = $paths['color'] .'/'. $id; foreach ($paths as $path) { file_check_directory($path, FILE_CREATE_DIRECTORY); } $paths['target'] = $paths['target'] .'/'; $paths['id'] = $id; $paths['source'] = drupal_get_path('theme', $theme) .'/'; $paths['stylesheet'] = $paths['target'] .'style.css'; $paths['files'] = $paths['map'] = array(); // Save palette and stylesheet location variable_set('color_'. $theme .'_palette', $palette); variable_set('color_'. $theme .'_stylesheet', $paths['stylesheet']); variable_set('color_'. $theme .'_logo', $paths['target'] .'logo.png'); // Copy over neutral images foreach ($info['copy'] as $file) { $base = basename($file); $source = $paths['source'] . $file; file_copy($source, $paths['target'] . $base); $paths['map'][$file] = $base; $paths['files'][] = $paths['target'] . $base; } // Render new images _color_render_images($theme, $info, $paths, $palette); // Rewrite stylesheet _color_rewrite_stylesheet($theme, $info, $paths, $palette); // Maintain list of files variable_set('color_'. $theme .'_files', $paths['files']); } /** * Rewrite the stylesheet to match the colors in the palette. */ function _color_rewrite_stylesheet($theme, &$info, &$paths, $palette) { // Load stylesheet $style = file_get_contents($paths['source'] .'style.css'); // Prepare color conversion table $conversion = $palette; unset($conversion['base']); foreach ($conversion as $k => $v) { $conversion[$k] = drupal_strtolower($v); } $default = color_get_palette($theme, true); // Split off the "Don't touch" section of the stylesheet. list($style, $fixed) = explode("Color Module: Don't touch", $style); // Look for @import commands and insert the referenced stylesheets. $cwd = getcwd(); chdir(drupal_get_path('theme', $theme)); $style = preg_replace_callback('/@import\s*["\']([^"\']+)["\'];/', '_color_import_stylesheet', $style); chdir($cwd); // Find all colors in the stylesheet and the chunks in between. $style = preg_split('/(#[0-9a-f]{6}|#[0-9a-f]{3})/i', $style, -1, PREG_SPLIT_DELIM_CAPTURE); $is_color = false; $output = ''; $base = 'base'; // Iterate over all parts foreach ($style as $chunk) { if ($is_color) { $chunk = drupal_strtolower($chunk); // Check if this is one of the colors in the default palette if ($key = array_search($chunk, $default)) { $chunk = $conversion[$key]; } // Not a pre-set color. Extrapolate from the base. else { $chunk = _color_shift($palette[$base], $default[$base], $chunk, $info['blend_target']); } } else { // Determine the most suitable base color for the next color. // 'a' declarations. Use link. if (preg_match('@[^a-z0-9_-](a)[^a-z0-9_-][^/{]*{[^{]+$@i', $chunk)) { $base = 'link'; } // 'color:' styles. Use text. else if (preg_match('/(? $after) { $output = str_replace($before, $after, $output); } // Write new stylesheet $file = fopen($paths['stylesheet'], 'w+'); fwrite($file, $output); fclose($file); $paths['files'][] = $paths['stylesheet']; // Set standard file permissions for webserver-generated files @chmod($paths['stylesheet'], 0664); } /** * Helper function for _color_rewrite_stylesheet. */ function _color_import_stylesheet($matches) { return preg_replace('/url\(([\'"]?)(?![a-z]+:)/i', 'url(\1'. dirname($matches[1]) .'/', file_get_contents($matches[1])); } /** * Render images that match a given palette. */ function _color_render_images($theme, &$info, &$paths, $palette) { // Prepare template image. $source = $paths['source'] .'/'. $info['base_image']; $source = imagecreatefrompng($source); $width = imagesx($source); $height = imagesy($source); // Prepare target buffer. $target = imagecreatetruecolor($width, $height); imagealphablending($target, true); // Fill regions of solid color. foreach ($info['fill'] as $color => $fill) { imagefilledrectangle($target, $fill[0], $fill[1], $fill[0] + $fill[2], $fill[1] + $fill[3], _color_gd($target, $palette[$color])); } // Render gradient. for ($y = 0; $y < $info['gradient'][3]; ++$y) { $color = _color_blend($target, $palette['top'], $palette['bottom'], $y / ($info['gradient'][3] - 1)); imagefilledrectangle($target, $info['gradient'][0], $info['gradient'][1] + $y, $info['gradient'][0] + $info['gradient'][2], $info['gradient'][1] + $y + 1, $color); } // Blend over template. imagecopy($target, $source, 0, 0, 0, 0, $width, $height); // Clean up template image. imagedestroy($source); // Cut out slices. foreach ($info['slices'] as $file => $coord) { list($x, $y, $width, $height) = $coord; $base = basename($file); $image = $paths['target'] . $base; // Cut out slice. if ($file == 'screenshot.png') { $slice = imagecreatetruecolor(150, 90); imagecopyresampled($slice, $target, 0, 0, $x, $y, 150, 90, $width, $height); variable_set('color_'. $theme .'_screenshot', $image); } else { $slice = imagecreatetruecolor($width, $height); imagecopy($slice, $target, 0, 0, $x, $y, $width, $height); } // Save image. imagepng($slice, $image); imagedestroy($slice); $paths['files'][] = $image; // Set standard file permissions for webserver-generated files @chmod(realpath($image), 0664); // Build before/after map of image paths. $paths['map'][$file] = $base; } // Clean up target buffer. imagedestroy($target); } /** * Shift a given color, using a reference pair and a target blend color. * * Note: this function is significantly different from the JS version, as it * is written to match the blended images perfectly. * * Constraint: if (ref2 == target + (ref1 - target) * delta) for some fraction delta * then (return == target + (given - target) * delta) * * Loose constraint: Preserve relative positions in saturation and luminance * space. */ function _color_shift($given, $ref1, $ref2, $target) { // We assume that ref2 is a blend of ref1 and target and find // delta based on the length of the difference vectors: // delta = 1 - |ref2 - ref1| / |white - ref1| $target = _color_unpack($target, true); $ref1 = _color_unpack($ref1, true); $ref2 = _color_unpack($ref2, true); for ($i = 0; $i < 3; ++$i) { $numerator += ($ref2[$i] - $ref1[$i]) * ($ref2[$i] - $ref1[$i]); $denominator += ($target[$i] - $ref1[$i]) * ($target[$i] - $ref1[$i]); } $delta = ($denominator > 0) ? (1 - sqrt($numerator / $denominator)) : 0; // Calculate the color that ref2 would be if the assumption was true. for ($i = 0; $i < 3; ++$i) { $ref3[$i] = $target[$i] + ($ref1[$i] - $target[$i]) * $delta; } // If the assumption is not true, there is a difference between ref2 and ref3. // We measure this in HSL space. Notation: x' = hsl(x). $ref2 = _color_rgb2hsl($ref2); $ref3 = _color_rgb2hsl($ref3); for ($i = 0; $i < 3; ++$i) { $shift[$i] = $ref2[$i] - $ref3[$i]; } // Take the given color, and blend it towards the target. $given = _color_unpack($given, true); for ($i = 0; $i < 3; ++$i) { $result[$i] = $target[$i] + ($given[$i] - $target[$i]) * $delta; } // Finally, we apply the extra shift in HSL space. // Note: if ref2 is a pure blend of ref1 and target, then |shift| = 0. $result = _color_rgb2hsl($result); for ($i = 0; $i < 3; ++$i) { $result[$i] = min(1, max(0, $result[$i] + $shift[$i])); } $result = _color_hsl2rgb($result); // Return hex color. return _color_pack($result, true); } /** * Convert a hex triplet into a GD color. */ function _color_gd($img, $hex) { $c = array_merge(array($img), _color_unpack($hex)); return call_user_func_array('imagecolorallocate', $c); } /** * Blend two hex colors and return the GD color. */ function _color_blend($img, $hex1, $hex2, $alpha) { $in1 = _color_unpack($hex1); $in2 = _color_unpack($hex2); $out = array($img); for ($i = 0; $i < 3; ++$i) { $out[] = $in1[$i] + ($in2[$i] - $in1[$i]) * $alpha; } return call_user_func_array('imagecolorallocate', $out); } /** * Convert a hex color into an RGB triplet. */ function _color_unpack($hex, $normalize = false) { if (strlen($hex) == 4) { $hex = $hex[1] . $hex[1] . $hex[2] . $hex[2] . $hex[3] . $hex[3]; } $c = hexdec($hex); for ($i = 16; $i >= 0; $i -= 8) { $out[] = (($c >> $i) & 0xFF) / ($normalize ? 255 : 1); } return $out; } /** * Convert an RGB triplet to a hex color. */ function _color_pack($rgb, $normalize = false) { foreach ($rgb as $k => $v) { $out |= (($v * ($normalize ? 255 : 1)) << (16 - $k * 8)); } return '#'. str_pad(dechex($out), 6, 0, STR_PAD_LEFT); } /** * Convert a HSL triplet into RGB */ function _color_hsl2rgb($hsl) { $h = $hsl[0]; $s = $hsl[1]; $l = $hsl[2]; $m2 = ($l <= 0.5) ? $l * ($s + 1) : $l + $s - $l*$s; $m1 = $l * 2 - $m2; return array(_color_hue2rgb($m1, $m2, $h + 0.33333), _color_hue2rgb($m1, $m2, $h), _color_hue2rgb($m1, $m2, $h - 0.33333)); } /** * Helper function for _color_hsl2rgb(). */ function _color_hue2rgb($m1, $m2, $h) { $h = ($h < 0) ? $h + 1 : (($h > 1) ? $h - 1 : $h); if ($h * 6 < 1) return $m1 + ($m2 - $m1) * $h * 6; if ($h * 2 < 1) return $m2; if ($h * 3 < 2) return $m1 + ($m2 - $m1) * (0.66666 - $h) * 6; return $m1; } /** * Convert an RGB triplet to HSL. */ function _color_rgb2hsl($rgb) { $r = $rgb[0]; $g = $rgb[1]; $b = $rgb[2]; $min = min($r, min($g, $b)); $max = max($r, max($g, $b)); $delta = $max - $min; $l = ($min + $max) / 2; $s = 0; if ($l > 0 && $l < 1) { $s = $delta / ($l < 0.5 ? (2 * $l) : (2 - 2 * $l)); } $h = 0; if ($delta > 0) { if ($max == $r && $max != $g) $h += ($g - $b) / $delta; if ($max == $g && $max != $b) $h += (2 + ($b - $r) / $delta); if ($max == $b && $max != $r) $h += (4 + ($r - $g) / $delta); $h /= 6; } return array($h, $s, $l); } loki_website/modules/color/color.css0000644000004100000410000000242310611311322020173 0ustar www-datawww-data/* $Id: color.css,v 1.2.2.1 2007/04/18 03:38:58 drumm Exp $ */ /* Farbtastic placement */ .color-form { max-width: 50em; position: relative; } #placeholder { position: absolute; top: 0; right: 0; } /* Palette */ .color-form .form-item { height: 2em; line-height: 2em; padding-left: 1em; margin: 0.5em 0; } .color-form label { float: left; clear: left; width: 10em; } .color-form .form-text, .color-form .form-select { float: left; } .color-form .form-text { text-align: center; margin-right: 5px; cursor: pointer; } #palette .hook { float: left; margin-top: 3px; width: 16px; height: 16px; } #palette .down, #palette .up, #palette .both { background: url(images/hook.png) no-repeat 100% 0; } #palette .up { background-position: 100% -27px; } #palette .both { background-position: 100% -54px; } #palette .lock { float: left; position: relative; top: -1.4em; left: -10px; width: 20px; height: 25px; background: url(images/lock.png) no-repeat 50% 2px; cursor: pointer; } #palette .unlocked { background-position: 50% -22px; } #palette .form-item { width: 20em; } #palette .item-selected { background: #eee; } /* Preview */ #preview { display: none; } html.js #preview { display: block; position: relative; float: left; } loki_website/modules/color/color.info0000644000004100000410000000047410741515024020353 0ustar www-datawww-data; $Id: color.info,v 1.2 2006/11/21 20:55:34 dries Exp $ name = Color description = Allows the user to change the color scheme of certain themes. package = Core - optional version = VERSION ; Information added by drupal.org packaging script on 2008-01-10 version = "5.6" project = "drupal" datestamp = "1200003604" loki_website/modules/color/color.js0000644000004100000410000001577310611311322020033 0ustar www-datawww-data// $Id: color.js,v 1.1.2.1 2007/04/18 03:38:58 drumm Exp $ if (Drupal.jsEnabled) { $(document).ready(function () { var form = $('#color_scheme_form .color-form'); var inputs = []; var hooks = []; var locks = []; var focused = null; // Add Farbtastic $(form).prepend('
'); var farb = $.farbtastic('#placeholder'); // Decode reference colors to HSL var reference = Drupal.settings.color.reference; for (i in reference) { reference[i] = farb.RGBToHSL(farb.unpack(reference[i])); } // Build preview $('#preview').append('
'); var gradient = $('#preview #gradient'); var h = parseInt(gradient.css('height')) / 10; for (i = 0; i < h; ++i) { gradient.append('
'); } // Fix preview background in IE6 if (navigator.appVersion.match(/MSIE [0-6]\./)) { var e = $('#preview #img')[0]; var image = e.currentStyle.backgroundImage; e.style.backgroundImage = 'none'; e.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='" + image.substring(5, image.length - 2) + "')"; } // Set up colorscheme selector $('#edit-scheme', form).change(function () { var colors = this.options[this.selectedIndex].value; if (colors != '') { colors = colors.split(','); for (i in colors) { callback(inputs[i], colors[i], false, true); } preview(); } }); /** * Render the preview. */ function preview() { // Solid background $('#preview', form).css('backgroundColor', inputs[0].value); // Text preview $('#text', form).css('color', inputs[4].value); $('#text a, #text h2', form).css('color', inputs[1].value); // Set up gradient var top = farb.unpack(inputs[2].value); var bottom = farb.unpack(inputs[3].value); if (top && bottom) { var delta = []; for (i in top) { delta[i] = (bottom[i] - top[i]) / h; } var accum = top; // Render gradient lines $('#gradient > div', form).each(function () { for (i in accum) { accum[i] += delta[i]; } this.style.backgroundColor = farb.pack(accum); }); } } /** * Shift a given color, using a reference pair (ref in HSL). * * This algorithm ensures relative ordering on the saturation and luminance * axes is preserved, and performs a simple hue shift. * * It is also symmetrical. If: shift_color(c, a, b) == d, * then shift_color(d, b, a) == c. */ function shift_color(given, ref1, ref2) { // Convert to HSL given = farb.RGBToHSL(farb.unpack(given)); // Hue: apply delta given[0] += ref2[0] - ref1[0]; // Saturation: interpolate if (ref1[1] == 0 || ref2[1] == 0) { given[1] = ref2[1]; } else { var d = ref1[1] / ref2[1]; if (d > 1) { given[1] /= d; } else { given[1] = 1 - (1 - given[1]) * d; } } // Luminance: interpolate if (ref1[2] == 0 || ref2[2] == 0) { given[2] = ref2[2]; } else { var d = ref1[2] / ref2[2]; if (d > 1) { given[2] /= d; } else { given[2] = 1 - (1 - given[2]) * d; } } return farb.pack(farb.HSLToRGB(given)); } /** * Callback for Farbtastic when a new color is chosen. */ function callback(input, color, propagate, colorscheme) { // Set background/foreground color $(input).css({ backgroundColor: color, color: farb.RGBToHSL(farb.unpack(color))[2] > 0.5 ? '#000' : '#fff' }); // Change input value if (input.value && input.value != color) { input.value = color; // Update locked values if (propagate) { var i = input.i; for (j = i + 1; ; ++j) { if (!locks[j - 1] || $(locks[j - 1]).is('.unlocked')) break; var matched = shift_color(color, reference[input.key], reference[inputs[j].key]); callback(inputs[j], matched, false); } for (j = i - 1; ; --j) { if (!locks[j] || $(locks[j]).is('.unlocked')) break; var matched = shift_color(color, reference[input.key], reference[inputs[j].key]); callback(inputs[j], matched, false); } // Update preview preview(); } // Reset colorscheme selector if (!colorscheme) { resetScheme(); } } } /** * Reset the color scheme selector. */ function resetScheme() { $('#edit-scheme', form).each(function () { this.selectedIndex = this.options.length - 1; }); } // Focus the Farbtastic on a particular field. function focus() { var input = this; // Remove old bindings focused && $(focused).unbind('keyup', farb.updateValue) .unbind('keyup', preview).unbind('keyup', resetScheme) .parent().removeClass('item-selected'); // Add new bindings focused = this; farb.linkTo(function (color) { callback(input, color, true, false) }); farb.setColor(this.value); $(focused).keyup(farb.updateValue).keyup(preview).keyup(resetScheme) .parent().addClass('item-selected'); } // Initialize color fields $('#palette input.form-text', form) .each(function () { // Extract palette field name this.key = this.id.substring(13); // Link to color picker temporarily to initialize. farb.linkTo(function () {}).setColor('#000').linkTo(this); // Add lock var i = inputs.length; if (inputs.length) { var lock = $('
').toggle( function () { $(this).addClass('unlocked'); $(hooks[i - 1]).attr('class', locks[i - 2] && $(locks[i - 2]).is(':not(.unlocked)') ? 'hook up' : 'hook' ); $(hooks[i]).attr('class', locks[i] && $(locks[i]).is(':not(.unlocked)') ? 'hook down' : 'hook' ); }, function () { $(this).removeClass('unlocked'); $(hooks[i - 1]).attr('class', locks[i - 2] && $(locks[i - 2]).is(':not(.unlocked)') ? 'hook both' : 'hook down' ); $(hooks[i]).attr('class', locks[i] && $(locks[i]).is(':not(.unlocked)') ? 'hook both' : 'hook up' ); } ); $(this).after(lock); locks.push(lock); } // Add hook var hook = $('
'); $(this).after(hook); hooks.push(hook); $(this).parent().find('.lock').click(); this.i = i; inputs.push(this); }) .focus(focus); $('#palette label', form) // Focus first color focus.call(inputs[0]); // Render preview preview(); }); }loki_website/modules/forum/0000755000004100000410000000000010744012100016353 5ustar www-datawww-dataloki_website/modules/forum/forum.install0000644000004100000410000000245710475761730021130 0ustar www-datawww-data'. t('The forum module lets you create threaded discussion forums for a particular topic on your site. This is similar to a message board system such as phpBB. Forums are very useful because they allow community members to discuss topics with one another, and they are archived for future reference.') .'

'; $output .= '

'. t('Forums can be organized under what are called containers. Containers hold forums and, in turn, forums hold threaded discussions. Both containers and forums can be placed inside other containers and forums. By planning the structure of your containers and forums well, you make it easier for users to find a topic area of interest to them. Forum topics can be moved by selecting a different forum and can be left in the existing forum by selecting leave a shadow copy. Forum topics can also have their own URL.') .'

'; $output .= '

'. t('Forums module requires Taxonomy and Comments module be enabled.') .'

'; $output .= '

'. t('For more information please read the configuration and customization handbook Forum page.', array('@forum' => 'http://drupal.org/handbook/modules/forum/')) .'

'; return $output; case 'admin/content/forum': return '

'. t('This is a list of existing containers and forums that you can edit. Containers hold forums and, in turn, forums hold threaded discussions. Both containers and forums can be placed inside other containers and forums. By planning the structure of your containers and forums well, you make it easier for users to find a topic area of interest to them.') .'

'; case 'admin/content/forum/add/container': return '

'. t('Containers help you organize your forums. The job of a container is to hold, or contain, other forums that are related. For example, a container named "Food" might hold two forums named "Fruit" and "Vegetables".') .'

'; case 'admin/content/forum/add/forum': return '

'. t('A forum holds discussion topics that are related. For example, a forum named "Fruit" might contain topics titled "Apples" and "Bananas".') .'

'; case 'admin/content/forum/settings': return '

'. t('These settings provide the ability to fine tune the display of your forum topics.') .'

'; } } /** * Implementation of hook_menu(). */ function forum_menu($may_cache) { $items = array(); if ($may_cache) { $items[] = array('path' => 'forum', 'title' => t('Forums'), 'callback' => 'forum_page', 'access' => user_access('access content'), 'type' => MENU_SUGGESTED_ITEM); $items[] = array('path' => 'admin/content/forum', 'title' => t('Forums'), 'description' => t('Control forums and their hierarchy and change forum settings.'), 'callback' => 'forum_overview', 'access' => user_access('administer forums'), 'type' => MENU_NORMAL_ITEM); $items[] = array('path' => 'admin/content/forum/list', 'title' => t('List'), 'access' => user_access('administer forums'), 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10); $items[] = array('path' => 'admin/content/forum/add/container', 'title' => t('Add container'), 'callback' => 'forum_form_main', 'callback arguments' => array('container'), 'access' => user_access('administer forums'), 'type' => MENU_LOCAL_TASK); $items[] = array('path' => 'admin/content/forum/add/forum', 'title' => t('Add forum'), 'callback' => 'forum_form_main', 'callback arguments' => array('forum'), 'access' => user_access('administer forums'), 'type' => MENU_LOCAL_TASK); $items[] = array('path' => 'admin/content/forum/settings', 'title' => t('Settings'), 'callback' => 'drupal_get_form', 'callback arguments' => array('forum_admin_settings'), 'weight' => 5, 'access' => user_access('administer forums'), 'type' => MENU_LOCAL_TASK); } elseif (is_numeric(arg(5))) { $term = taxonomy_get_term(arg(5)); // Check if this is a valid term. if ($term) { $items[] = array('path' => 'admin/content/forum/edit/container', 'title' => t('Edit container'), 'callback' => 'forum_form_main', 'callback arguments' => array('container', (array)$term), 'access' => user_access('administer forums'), 'type' => MENU_CALLBACK); $items[] = array('path' => 'admin/content/forum/edit/forum', 'title' => t('Edit forum'), 'callback' => 'forum_form_main', 'callback arguments' => array('forum', (array)$term), 'access' => user_access('administer forums'), 'type' => MENU_CALLBACK); } } return $items; } /** * Implementation of hook_node_info(). */ function forum_node_info() { return array( 'forum' => array( 'name' => t('Forum topic'), 'module' => 'forum', 'description' => t('Create a new topic for discussion in the forums.'), 'title_label' => t('Subject'), ) ); } /** * Implementation of hook_access(). */ function forum_access($op, $node) { global $user; if ($op == 'create') { return user_access('create forum topics'); } if ($op == 'update' || $op == 'delete') { if (user_access('edit own forum topics') && ($user->uid == $node->uid)) { return TRUE; } } } /** * Implementation of hook_perm(). */ function forum_perm() { return array('create forum topics', 'edit own forum topics', 'administer forums'); } /** * Implementation of hook_nodeapi(). */ function forum_nodeapi(&$node, $op, $teaser, $page) { switch ($op) { case 'delete revision': db_query('DELETE FROM {forum} WHERE vid = %d', $node->vid); break; } } /** * Implementation of hook_taxonomy(). */ function forum_taxonomy($op, $type, $term = NULL) { if ($op == 'delete' && $term['vid'] == _forum_get_vid()) { switch ($type) { case 'term': $results = db_query('SELECT f.nid FROM {forum} f WHERE f.tid = %d', $term['tid']); while ($node = db_fetch_object($results)) { // node_delete will also remove any association with non-forum vocabularies. node_delete($node->nid); } // For containers, remove the tid from the forum_containers variable. $containers = variable_get('forum_containers', array()); $key = array_search($term['tid'], $containers); if ($key !== FALSE) { unset($containers[$key]); } variable_set('forum_containers', $containers); break; case 'vocabulary': variable_del('forum_nav_vocabulary'); } } } function forum_admin_settings() { $form = array(); $number = drupal_map_assoc(array(5, 10, 15, 20, 25, 30, 35, 40, 50, 60, 80, 100, 150, 200, 250, 300, 350, 400, 500)); $form['forum_hot_topic'] = array('#type' => 'select', '#title' => t('Hot topic threshold'), '#default_value' => variable_get('forum_hot_topic', 15), '#options' => $number, '#description' => t('The number of posts a topic must have to be considered hot.'), ); $number = drupal_map_assoc(array(10, 25, 50, 75, 100)); $form['forum_per_page'] = array('#type' => 'select', '#title' => t('Topics per page'), '#default_value' => variable_get('forum_per_page', 25), '#options' => $number, '#description' => t('The default number of topics displayed per page; links to browse older messages are automatically being displayed.'), ); $forder = array(1 => t('Date - newest first'), 2 => t('Date - oldest first'), 3 => t('Posts - most active first'), 4=> t('Posts - least active first')); $form['forum_order'] = array('#type' => 'radios', '#title' => t('Default order'), '#default_value' => variable_get('forum_order', '1'), '#options' => $forder, '#description' => t('The default display order for topics.'), ); return system_settings_form($form); } /** * Implementation of hook_form_alter(). */ function forum_form_alter($form_id, &$form) { // hide critical options from forum vocabulary if ($form_id == 'taxonomy_form_vocabulary') { if ($form['vid']['#value'] == _forum_get_vid()) { $form['help_forum_vocab'] = array( '#value' => t('This is the designated forum vocabulary. Some of the normal vocabulary options have been removed.'), '#weight' => -1, ); $form['nodes']['forum'] = array('#type' => 'checkbox', '#value' => 1, '#title' => t('forum topic'), '#attributes' => array('disabled' => '' ), '#description' => t('forum topic is affixed to the forum vocabulary.')); $form['hierarchy'] = array('#type' => 'value', '#value' => 1); unset($form['relations']); unset($form['tags']); unset($form['multiple']); $form['required'] = array('#type' => 'value', '#value' => 1); } else { unset($form['nodes']['forum']); } } } /** * Implementation of hook_load(). */ function forum_load($node) { $forum = db_fetch_object(db_query('SELECT * FROM {forum} WHERE vid = %d', $node->vid)); return $forum; } /** * Implementation of hook_block(). * * Generates a block containing the currently active forum topics and the * most recently added forum topics. */ function forum_block($op = 'list', $delta = 0, $edit = array()) { switch ($op) { case 'list': $blocks[0]['info'] = t('Active forum topics'); $blocks[1]['info'] = t('New forum topics'); return $blocks; case 'configure': $form['forum_block_num_'. $delta] = array('#type' => 'select', '#title' => t('Number of topics'), '#default_value' => variable_get('forum_block_num_'. $delta, '5'), '#options' => drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20))); return $form; case 'save': variable_set('forum_block_num_'. $delta, $edit['forum_block_num_'. $delta]); break; case 'view': if (user_access('access content')) { switch ($delta) { case 0: $title = t('Active forum topics'); $sql = db_rewrite_sql("SELECT n.nid, n.title, l.comment_count, l.last_comment_timestamp FROM {node} n INNER JOIN {node_comment_statistics} l ON n.nid = l.nid WHERE n.status = 1 AND n.type = 'forum' ORDER BY l.last_comment_timestamp DESC"); $result = db_query_range($sql, 0, variable_get('forum_block_num_0', '5')); if (db_num_rows($result)) { $content = node_title_list($result); } break; case 1: $title = t('New forum topics'); $sql = db_rewrite_sql("SELECT n.nid, n.title, l.comment_count FROM {node} n INNER JOIN {node_comment_statistics} l ON n.nid = l.nid WHERE n.type = 'forum' AND n.status = 1 ORDER BY n.nid DESC"); $result = db_query_range($sql, 0, variable_get('forum_block_num_1', '5')); if (db_num_rows($result)) { $content = node_title_list($result); } break; } if ($content) { $content .= ''; } $block['subject'] = $title; $block['content'] = $content; return $block; } } } /** * Implementation of hook_view(). */ function forum_view(&$node, $teaser = FALSE, $page = FALSE) { drupal_add_css(drupal_get_path('module', 'forum') .'/forum.css'); if ($page) { $vocabulary = taxonomy_get_vocabulary(variable_get('forum_nav_vocabulary', '')); // Breadcrumb navigation $breadcrumb = array(); $breadcrumb[] = array('path' => 'forum', 'title' => $vocabulary->name); if ($parents = taxonomy_get_parents_all($node->tid)) { $parents = array_reverse($parents); foreach ($parents as $p) { $breadcrumb[] = array('path' => 'forum/'. $p->tid, 'title' => $p->name); } } $breadcrumb[] = array('path' => 'node/'. $node->nid); menu_set_location($breadcrumb); } $node = node_prepare($node, $teaser); if (!$teaser) { $node->content['forum_navigation'] = array( '#value' => theme('forum_topic_navigation', $node), '#weight' => 100, ); } return $node; } /** * Implementation of hook_submit(). * * Check in particular that only a "leaf" term in the associated taxonomy * vocabulary is selected, not a "container" term. */ function forum_submit(&$node) { // Make sure all fields are set properly: $node->icon = !empty($node->icon) ? $node->icon : ''; if ($node->taxonomy) { // Get the forum terms from the (cached) tree $tree = taxonomy_get_tree(_forum_get_vid()); if ($tree) { foreach ($tree as $term) { $forum_terms[] = $term->tid; } } foreach ($node->taxonomy as $term) { if (in_array($term, $forum_terms)) { $node->tid = $term; } } $old_tid = db_result(db_query_range("SELECT tid FROM {forum} WHERE nid = %d ORDER BY vid DESC", $node->nid, 0,1)); if ($old_tid) { if (($node->tid != $old_tid) && $node->shadow) { // A shadow copy needs to be created. Retain new term and add old term. $node->taxonomy[] = $old_tid; } } } } /** * Implementation of hook_validate(). * * Check in particular that only a "leaf" term in the associated taxonomy * vocabulary is selected, not a "container" term. */ function forum_validate($node) { if ($node->taxonomy) { // Extract the node's proper topic ID. $vocabulary = variable_get('forum_nav_vocabulary', ''); $containers = variable_get('forum_containers', array()); foreach ($node->taxonomy as $term) { if (db_result(db_query('SELECT COUNT(*) FROM {term_data} WHERE tid = %d AND vid = %d', $term, $vocabulary))) { if (in_array($term, $containers)) { $term = taxonomy_get_term($term); form_set_error('taxonomy', t('The item %forum is only a container for forums. Please select one of the forums below it.', array('%forum' => $term->name))); } } } } } /** * Implementation of hook_update(). */ function forum_update($node) { if ($node->revision) { db_query("INSERT INTO {forum} (nid, vid, tid) VALUES (%d, %d, %d)", $node->nid, $node->vid, $node->tid); } else { db_query('UPDATE {forum} SET tid = %d WHERE vid = %d', $node->tid, $node->vid); } } /** * Implementation of hook_form(). */ function forum_form(&$node) { $type = node_get_types('type', $node); $form['title'] = array('#type' => 'textfield', '#title' => check_plain($type->title_label), '#default_value' => $node->title, '#required' => TRUE, '#weight' => -5); if ($node->nid) { $forum_terms = taxonomy_node_get_terms_by_vocabulary(_forum_get_vid(), $node->nid); // if editing, give option to leave shadows $shadow = (count($forum_terms) > 1); $form['shadow'] = array('#type' => 'checkbox', '#title' => t('Leave shadow copy'), '#default_value' => $shadow, '#description' => t('If you move this topic, you can leave a link in the old forum to the new forum.')); } $form['body_filter']['body'] = array('#type' => 'textarea', '#title' => check_plain($type->body_label), '#default_value' => $node->body, '#rows' => 20, '#required' => TRUE); $form['body_filter']['format'] = filter_form($node->format); return $form; } /** * Implementation of hook_prepare; assign forum taxonomy when adding a topic from within a forum. */ function forum_prepare(&$node) { if (!$node->nid) { // new topic $node->taxonomy[arg(3)]->vid = _forum_get_vid(); $node->taxonomy[arg(3)]->tid = arg(3); } } /** * Implementation of hook_insert(). */ function forum_insert($node) { db_query('INSERT INTO {forum} (nid, vid, tid) VALUES (%d, %d, %d)', $node->nid, $node->vid, $node->tid); } /** * Implementation of hook_delete(). */ function forum_delete(&$node) { db_query('DELETE FROM {forum} WHERE nid = %d', $node->nid); } /** * Returns a form for adding a container to the forum vocabulary * * @param $edit Associative array containing a container term to be added or edited. */ function forum_form_container($edit = array()) { // Handle a delete operation. $form['name'] = array( '#title' => t('Container name'), '#type' => 'textfield', '#default_value' => $edit['name'], '#maxlength' => 64, '#description' => t('The container name is used to identify related forums.'), '#required' => TRUE ); $form['description'] = array( '#type' => 'textarea', '#title' => t('Description'), '#default_value' => $edit['description'], '#description' => t('The container description can give users more information about the forums it contains.') ); $form['parent']['#tree'] = TRUE; $form['parent'][0] = _forum_parent_select($edit['tid'], t('Parent'), 'container'); $form['weight'] = array('#type' => 'weight', '#title' => t('Weight'), '#default_value' => $edit['weight'], '#description' => t('When listing containers, those with with light (small) weights get listed before containers with heavier (larger) weights. Containers with equal weights are sorted alphabetically.') ); $form['vid'] = array('#type' => 'hidden', '#value' => _forum_get_vid()); $form['submit'] = array( '#type' => 'submit', '#value' => t('Submit') ); if ($edit['tid']) { $form['delete'] = array('#type' => 'submit', '#value' => t('Delete')); $form['tid'] = array('#type' => 'value', '#value' => $edit['tid']); } $form['#base'] = 'forum_form'; return $form; } function forum_form_main($type, $edit = array()) { if ($_POST['op'] == t('Delete') || $_POST['confirm']) { return drupal_get_form('forum_confirm_delete', $edit['tid']); } switch ($type) { case 'forum': return drupal_get_form('forum_form_forum', $edit); break; case 'container': return drupal_get_form('forum_form_container', $edit); break; } } /** * Returns a form for adding a forum to the forum vocabulary * * @param $edit Associative array containing a forum term to be added or edited. */ function forum_form_forum($edit = array()) { $form['name'] = array('#type' => 'textfield', '#title' => t('Forum name'), '#default_value' => $edit['name'], '#maxlength' => 64, '#description' => t('The forum name is used to identify related discussions.'), '#required' => TRUE, ); $form['description'] = array('#type' => 'textarea', '#title' => t('Description'), '#default_value' => $edit['description'], '#description' => t('The forum description can give users more information about the discussion topics it contains.'), ); $form['parent']['#tree'] = TRUE; $form['parent'][0] = _forum_parent_select($edit['tid'], t('Parent'), 'forum'); $form['weight'] = array('#type' => 'weight', '#title' => t('Weight'), '#default_value' => $edit['weight'], '#description' => t('When listing forums, those with lighter (smaller) weights get listed before containers with heavier (larger) weights. Forums with equal weights are sorted alphabetically.'), ); $form['vid'] = array('#type' => 'hidden', '#value' => _forum_get_vid()); $form['submit' ] = array('#type' => 'submit', '#value' => t('Submit')); if ($edit['tid']) { $form['delete'] = array('#type' => 'submit', '#value' => t('Delete')); $form['tid'] = array('#type' => 'hidden', '#value' => $edit['tid']); } $form['#base'] = 'forum_form'; return $form; } /** * Process forum form and container form submissions. */ function forum_form_submit($form_id, $form_values) { if ($form_id == 'forum_form_container') { $container = TRUE; $type = t('forum container'); } else { $container = FALSE; $type = t('forum'); } $status = taxonomy_save_term($form_values); switch ($status) { case SAVED_NEW: if ($container) { $containers = variable_get('forum_containers', array()); $containers[] = $form_values['tid']; variable_set('forum_containers', $containers); } drupal_set_message(t('Created new @type %term.', array('%term' => $form_values['name'], '@type' => $type))); break; case SAVED_UPDATED: drupal_set_message(t('The @type %term has been updated.', array('%term' => $form_values['name'], '@type' => $type))); break; } return 'admin/content/forum'; } /** * Returns a confirmation page for deleting a forum taxonomy term. * * @param $tid ID of the term to be deleted */ function forum_confirm_delete($tid) { $term = taxonomy_get_term($tid); $form['tid'] = array('#type' => 'value', '#value' => $tid); $form['name'] = array('#type' => 'value', '#value' => $term->name); return confirm_form($form, t('Are you sure you want to delete the forum %name?', array('%name' => $term->name)), 'admin/content/forum', t('Deleting a forum or container will delete all sub-forums and associated posts as well. This action cannot be undone.'), t('Delete'), t('Cancel')); } /** * Implementation of forms api _submit call. Deletes a forum after confirmation. */ function forum_confirm_delete_submit($form_id, $form_values) { taxonomy_del_term($form_values['tid']); drupal_set_message(t('The forum %term and all sub-forums and associated posts have been deleted.', array('%term' => $form_values['name']))); watchdog('content', t('forum: deleted %term and all its sub-forums and associated posts.', array('%term' => $form_values['name']))); return 'admin/content/forum'; } /** * Returns an overview list of existing forums and containers */ function forum_overview() { $header = array(t('Name'), t('Operations')); $tree = taxonomy_get_tree(_forum_get_vid()); if ($tree) { foreach ($tree as $term) { if (in_array($term->tid, variable_get('forum_containers', array()))) { $rows[] = array(str_repeat(' -- ', $term->depth) .' '. l($term->name, 'forum/'. $term->tid), l(t('edit container'), 'admin/content/forum/edit/container/'. $term->tid)); } else { $rows[] = array(str_repeat(' -- ', $term->depth) .' '. l($term->name, 'forum/'. $term->tid), l(t('edit forum'), 'admin/content/forum/edit/forum/'. $term->tid)); } } } else { $rows[] = array(array('data' => '' . t('There are no existing containers or forums. You may add some on the add container or add forum pages.', array('@container' => url('admin/content/forum/add/container'), '@forum' => url('admin/content/forum/add/forum'))) . '', 'colspan' => 2)); } return theme('table', $header, $rows); } /** * Returns a select box for available parent terms * * @param $tid ID of the term which is being added or edited * @param $title Title to display the select box with * @param $child_type Whether the child is forum or container */ function _forum_parent_select($tid, $title, $child_type) { $parents = taxonomy_get_parents($tid); if ($parents) { $parent = array_shift($parents); $parent = $parent->tid; } else { $parent = 0; } $children = taxonomy_get_tree(_forum_get_vid(), $tid); // A term can't be the child of itself, nor of its children. foreach ($children as $child) { $exclude[] = $child->tid; } $exclude[] = $tid; $tree = taxonomy_get_tree(_forum_get_vid()); $options[0] = '<'. t('root') .'>'; if ($tree) { foreach ($tree as $term) { if (!in_array($term->tid, $exclude)) { $options[$term->tid] = str_repeat(' -- ', $term->depth) . $term->name; } } } if ($child_type == 'container') { $description = t('Containers are usually placed at the top (root) level of your forum but you can also place a container inside a parent container or forum.'); } else if ($child_type == 'forum') { $description = t('You may place your forum inside a parent container or forum, or at the top (root) level of your forum.'); } return array('#type' => 'select', '#title' => $title, '#default_value' => $parent, '#options' => $options, '#description' => $description, '#required' => TRUE); } function forum_link_alter(&$node, &$links) { foreach ($links as $module => $link) { if (strstr($module, 'taxonomy_term')) { // Link back to the forum and not the taxonomy term page. We'll only // do this if the taxonomy term in question belongs to forums. $tid = str_replace('taxonomy/term/', '', $link['href']); $term = taxonomy_get_term($tid); if ($term->vid == _forum_get_vid()) { $links[$module]['href'] = str_replace('taxonomy/term', 'forum', $link['href']); } } } } /** * Returns the vocabulary id for forum navigation. */ function _forum_get_vid() { $vid = variable_get('forum_nav_vocabulary', ''); if (empty($vid)) { // Check to see if a forum vocabulary exists $vid = db_result(db_query("SELECT vid FROM {vocabulary} WHERE module = '%s'", 'forum')); if (!$vid) { // Create the forum vocabulary. Assign the vocabulary a low weight so // it will appear first in forum topic create and edit forms. $edit = array('name' => t('Forums'), 'multiple' => 0, 'required' => 1, 'hierarchy' => 1, 'relations' => 0, 'module' => 'forum', 'weight' => -10, 'nodes' => array('forum' => 1)); taxonomy_save_vocabulary($edit); $vid = $edit['vid']; } variable_set('forum_nav_vocabulary', $vid); } return $vid; } /** * Formats a topic for display * * @TODO Give a better description. Not sure where this function is used yet. */ function _forum_format($topic) { if ($topic && $topic->timestamp) { return t('@time ago
by !author', array('@time' => format_interval(time() - $topic->timestamp), '!author' => theme('username', $topic))); } else { return t('n/a'); } } /** * Returns a list of all forums for a given taxonomy id * * Forum objects contain the following fields * -num_topics Number of topics in the forum * -num_posts Total number of posts in all topics * -last_post Most recent post for the forum * * @param $tid * Taxonomy ID of the vocabulary that holds the forum list. * @return * Array of object containing the forum information. */ function forum_get_forums($tid = 0) { $forums = array(); $_forums = taxonomy_get_tree(variable_get('forum_nav_vocabulary', ''), $tid); if (count($_forums)) { $counts = array(); $sql = "SELECT r.tid, COUNT(n.nid) AS topic_count, SUM(l.comment_count) AS comment_count FROM {node} n INNER JOIN {node_comment_statistics} l ON n.nid = l.nid INNER JOIN {term_node} r ON n.nid = r.nid WHERE n.status = 1 AND n.type = 'forum' GROUP BY r.tid"; $sql = db_rewrite_sql($sql); $_counts = db_query($sql, $forum->tid); while ($count = db_fetch_object($_counts)) { $counts[$count->tid] = $count; } } foreach ($_forums as $forum) { if (in_array($forum->tid, variable_get('forum_containers', array()))) { $forum->container = 1; } if ($counts[$forum->tid]) { $forum->num_topics = $counts[$forum->tid]->topic_count; $forum->num_posts = $counts[$forum->tid]->topic_count + $counts[$forum->tid]->comment_count; } else { $forum->num_topics = 0; $forum->num_posts = 0; } // This query does not use full ANSI syntax since MySQL 3.x does not support // table1 INNER JOIN table2 INNER JOIN table3 ON table2_criteria ON table3_criteria // used to join node_comment_statistics to users. $sql = "SELECT ncs.last_comment_timestamp, IF (ncs.last_comment_uid != 0, u2.name, ncs.last_comment_name) AS last_comment_name, ncs.last_comment_uid FROM {node} n INNER JOIN {users} u1 ON n.uid = u1.uid INNER JOIN {term_node} tn ON n.nid = tn.nid INNER JOIN {node_comment_statistics} ncs ON n.nid = ncs.nid INNER JOIN {users} u2 ON ncs.last_comment_uid=u2.uid WHERE n.status = 1 AND n.type='forum' AND tn.tid = %d ORDER BY ncs.last_comment_timestamp DESC"; $sql = db_rewrite_sql($sql); $topic = db_fetch_object(db_query_range($sql, $forum->tid, 0, 1)); $last_post = new stdClass(); $last_post->timestamp = $topic->last_comment_timestamp; $last_post->name = $topic->last_comment_name; $last_post->uid = $topic->last_comment_uid; $forum->last_post = $last_post; $forums[$forum->tid] = $forum; } return $forums; } /** * Calculate the number of nodes the user has not yet read and are newer * than NODE_NEW_LIMIT. */ function _forum_topics_unread($term, $uid) { $sql = "SELECT COUNT(n.nid) FROM {node} n INNER JOIN {term_node} tn ON n.nid = tn.nid AND tn.tid = %d LEFT JOIN {history} h ON n.nid = h.nid AND h.uid = %d WHERE n.status = 1 AND n.type = 'forum' AND n.created > %d AND h.nid IS NULL"; $sql = db_rewrite_sql($sql); return db_result(db_query($sql, $term, $uid, NODE_NEW_LIMIT)); } function forum_get_topics($tid, $sortby, $forum_per_page) { global $user, $forum_topic_list_header; $forum_topic_list_header = array( array('data' => ' ', 'field' => NULL), array('data' => t('Topic'), 'field' => 'n.title'), array('data' => t('Replies'), 'field' => 'l.comment_count'), array('data' => t('Created'), 'field' => 'n.created'), array('data' => t('Last reply'), 'field' => 'l.last_comment_timestamp'), ); $order = _forum_get_topic_order($sortby); for ($i = 0; $i < count($forum_topic_list_header); $i++) { if ($forum_topic_list_header[$i]['field'] == $order['field']) { $forum_topic_list_header[$i]['sort'] = $order['sort']; } } $term = taxonomy_get_term($tid); $sql = db_rewrite_sql("SELECT n.nid, f.tid, n.title, n.sticky, u.name, u.uid, n.created AS timestamp, n.comment AS comment_mode, l.last_comment_timestamp, IF(l.last_comment_uid != 0, cu.name, l.last_comment_name) AS last_comment_name, l.last_comment_uid, l.comment_count AS num_comments FROM {node_comment_statistics} l, {users} cu, {term_node} r, {users} u, {forum} f, {node} n WHERE n.status = 1 AND l.last_comment_uid = cu.uid AND n.nid = l.nid AND n.nid = r.nid AND r.tid = %d AND n.uid = u.uid AND n.vid = f.vid"); $sql .= tablesort_sql($forum_topic_list_header, 'n.sticky DESC,'); $sql .= ', n.created DESC'; // Always add a secondary sort order so that the news forum topics are on top. $sql_count = db_rewrite_sql("SELECT COUNT(n.nid) FROM {node} n INNER JOIN {term_node} r ON n.nid = r.nid AND r.tid = %d WHERE n.status = 1 AND n.type = 'forum'"); $result = pager_query($sql, $forum_per_page, 0, $sql_count, $tid); $topics = array(); while ($topic = db_fetch_object($result)) { if ($user->uid) { // folder is new if topic is new or there are new comments since last visit if ($topic->tid != $tid) { $topic->new = 0; } else { $history = _forum_user_last_visit($topic->nid); $topic->new_replies = comment_num_new($topic->nid, $history); $topic->new = $topic->new_replies || ($topic->timestamp > $history); } } else { // Do not track "new replies" status for topics if the user is anonymous. $topic->new_replies = 0; $topic->new = 0; } if ($topic->num_comments > 0) { $last_reply = new stdClass(); $last_reply->timestamp = $topic->last_comment_timestamp; $last_reply->name = $topic->last_comment_name; $last_reply->uid = $topic->last_comment_uid; $topic->last_reply = $last_reply; } $topics[] = $topic; } return $topics; } /** * Finds the first unread node for a given forum. */ function _forum_new($tid) { global $user; $sql = "SELECT n.nid FROM {node} n LEFT JOIN {history} h ON n.nid = h.nid AND h.uid = %d INNER JOIN {term_node} r ON n.nid = r.nid AND r.tid = %d WHERE n.status = 1 AND n.type = 'forum' AND h.nid IS NULL AND n.created > %d ORDER BY created"; $sql = db_rewrite_sql($sql); $nid = db_result(db_query_range($sql, $user->uid, $tid, NODE_NEW_LIMIT, 0, 1)); return $nid ? $nid : 0; } /** * Menu callback; prints a forum listing. */ function forum_page($tid = 0) { drupal_add_css(drupal_get_path('module', 'forum') .'/forum.css'); $forum_per_page = variable_get('forum_per_page', 25); $sortby = variable_get('forum_order', 1); $forums = forum_get_forums($tid); $parents = taxonomy_get_parents_all($tid); if ($tid && !in_array($tid, variable_get('forum_containers', array()))) { $topics = forum_get_topics($tid, $sortby, $forum_per_page); } return theme('forum_display', $forums, $topics, $parents, $tid, $sortby, $forum_per_page); } /** * Format the forum body. * * @ingroup themeable */ function theme_forum_display($forums, $topics, $parents, $tid, $sortby, $forum_per_page) { global $user; // forum list, topics list, topic browser and 'add new topic' link $vocabulary = taxonomy_get_vocabulary(variable_get('forum_nav_vocabulary', '')); $title = $vocabulary->name; // Breadcrumb navigation: $breadcrumb = array(); if ($tid) { $breadcrumb[] = array('path' => 'forum', 'title' => $title); } if ($parents) { $parents = array_reverse($parents); foreach ($parents as $p) { if ($p->tid == $tid) { $title = $p->name; } else { $breadcrumb[] = array('path' => 'forum/'. $p->tid, 'title' => $p->name); } } } drupal_set_title(check_plain($title)); $breadcrumb[] = array('path' => $_GET['q']); menu_set_location($breadcrumb); if (count($forums) || count($parents)) { $output = '
'; $output .= '
    '; if (user_access('create forum topics')) { $output .= '
  • '. l(t('Post new forum topic.'), "node/add/forum/$tid") .'
  • '; } else if ($user->uid) { $output .= '
  • '. t('You are not allowed to post a new forum topic.') .'
  • '; } else { $output .= '
  • '. t('Login to post a new forum topic.', array('@login' => url('user/login', drupal_get_destination()))) .'
  • '; } $output .= '
'; $output .= theme('forum_list', $forums, $parents, $tid); if ($tid && !in_array($tid, variable_get('forum_containers', array()))) { $output .= theme('forum_topic_list', $tid, $topics, $sortby, $forum_per_page); drupal_add_feed(url('taxonomy/term/'. $tid .'/0/feed'), 'RSS - '. $title); } $output .= '
'; } else { drupal_set_title(t('No forums defined')); $output = ''; } return $output; } /** * Format the forum listing. * * @ingroup themeable */ function theme_forum_list($forums, $parents, $tid) { global $user; if ($forums) { $header = array(t('Forum'), t('Topics'), t('Posts'), t('Last post')); foreach ($forums as $forum) { if ($forum->container) { $description = '
\n"; $description .= '
'. l($forum->name, "forum/$forum->tid") ."
\n"; if ($forum->description) { $description .= '
'. filter_xss_admin($forum->description) ."
\n"; } $description .= "
\n"; $rows[] = array(array('data' => $description, 'class' => 'container', 'colspan' => '4')); } else { $new_topics = _forum_topics_unread($forum->tid, $user->uid); $forum->old_topics = $forum->num_topics - $new_topics; if (!$user->uid) { $new_topics = 0; } $description = '
\n"; $description .= '
'. l($forum->name, "forum/$forum->tid") ."
\n"; if ($forum->description) { $description .= '
'. filter_xss_admin($forum->description) ."
\n"; } $description .= "
\n"; $rows[] = array( array('data' => $description, 'class' => 'forum'), array('data' => $forum->num_topics . ($new_topics ? '
'. l(format_plural($new_topics, '1 new', '@count new'), "forum/$forum->tid", NULL, NULL, 'new') : ''), 'class' => 'topics'), array('data' => $forum->num_posts, 'class' => 'posts'), array('data' => _forum_format($forum->last_post), 'class' => 'last-reply')); } } return theme('table', $header, $rows); } } /** * Format the topic listing. * * @ingroup themeable */ function theme_forum_topic_list($tid, $topics, $sortby, $forum_per_page) { global $forum_topic_list_header; $rows = array(); if ($topics) { foreach ($topics as $topic) { // folder is new if topic is new or there are new comments since last visit if ($topic->tid != $tid) { $rows[] = array( array('data' => theme('forum_icon', $topic->new, $topic->num_comments, $topic->comment_mode, $topic->sticky), 'class' => 'icon'), array('data' => check_plain($topic->title), 'class' => 'title'), array('data' => l(t('This topic has been moved'), "forum/$topic->tid"), 'colspan' => '3') ); } else { $rows[] = array( array('data' => theme('forum_icon', $topic->new, $topic->num_comments, $topic->comment_mode, $topic->sticky), 'class' => 'icon'), array('data' => l($topic->title, "node/$topic->nid"), 'class' => 'topic'), array('data' => $topic->num_comments . ($topic->new_replies ? '
'. l(format_plural($topic->new_replies, '1 new', '@count new'), "node/$topic->nid", NULL, NULL, 'new') : ''), 'class' => 'replies'), array('data' => _forum_format($topic), 'class' => 'created'), array('data' => _forum_format(isset($topic->last_reply) ? $topic->last_reply : NULL), 'class' => 'last-reply') ); } } } $output = theme('table', $forum_topic_list_header, $rows); $output .= theme('pager', NULL, $forum_per_page, 0); return $output; } /** * Format the icon for each individual topic. * * @ingroup themeable */ function theme_forum_icon($new_posts, $num_posts = 0, $comment_mode = 0, $sticky = 0) { if ($num_posts > variable_get('forum_hot_topic', 15)) { $icon = $new_posts ? 'hot-new' : 'hot'; } else { $icon = $new_posts ? 'new' : 'default'; } if ($comment_mode == COMMENT_NODE_READ_ONLY || $comment_mode == COMMENT_NODE_DISABLED) { $icon = 'closed'; } if ($sticky == 1) { $icon = 'sticky'; } $output = theme('image', "misc/forum-$icon.png"); if ($new_posts) { $output = "$output"; } return $output; } /** * Format the next/previous forum topic navigation links. * * @ingroup themeable */ function theme_forum_topic_navigation($node) { $output = ''; // get previous and next topic $sql = "SELECT n.nid, n.title, n.sticky, l.comment_count, l.last_comment_timestamp FROM {node} n INNER JOIN {node_comment_statistics} l ON n.nid = l.nid INNER JOIN {term_node} r ON n.nid = r.nid AND r.tid = %d WHERE n.status = 1 AND n.type = 'forum' ORDER BY n.sticky DESC, ". _forum_get_topic_order_sql(variable_get('forum_order', 1)); $result = db_query(db_rewrite_sql($sql), $node->tid); $stop = 0; while ($topic = db_fetch_object($result)) { if ($stop == 1) { $next = new stdClass(); $next->nid = $topic->nid; $next->title = $topic->title; break; } if ($topic->nid == $node->nid) { $stop = 1; } else { $prev = new stdClass(); $prev->nid = $topic->nid; $prev->title = $topic->title; } } if ($prev || $next) { $output .= '
'; if ($prev) { $output .= l(t('‹ ') . $prev->title, 'node/'. $prev->nid, array('class' => 'topic-previous', 'title' => t('Go to previous forum topic'))); } if ($prev && $next) { // Word break (a is an inline element) $output .= ' '; } if (!empty($next)) { $output .= l($next->title . t(' ›'), 'node/'. $next->nid, array('class' => 'topic-next', 'title' => t('Go to next forum topic'))); } $output .= '
'; } return $output; } function _forum_user_last_visit($nid) { global $user; static $history = array(); if (empty($history)) { $result = db_query('SELECT nid, timestamp FROM {history} WHERE uid = %d', $user->uid); while ($t = db_fetch_object($result)) { $history[$t->nid] = $t->timestamp > NODE_NEW_LIMIT ? $t->timestamp : NODE_NEW_LIMIT; } } return isset($history[$nid]) ? $history[$nid] : NODE_NEW_LIMIT; } function _forum_get_topic_order($sortby) { switch ($sortby) { case 1: return array('field' => 'l.last_comment_timestamp', 'sort' => 'desc'); break; case 2: return array('field' => 'l.last_comment_timestamp', 'sort' => 'asc'); break; case 3: return array('field' => 'l.comment_count', 'sort' => 'desc'); break; case 4: return array('field' => 'l.comment_count', 'sort' => 'asc'); break; } } function _forum_get_topic_order_sql($sortby) { $order = _forum_get_topic_order($sortby); return $order['field'] .' '. $order['sort']; } loki_website/modules/forum/forum.info0000644000004100000410000000052110741515024020370 0ustar www-datawww-data; $Id: forum.info,v 1.4 2006/11/21 20:55:34 dries Exp $ name = Forum description = Enables threaded discussions about general topics. dependencies = taxonomy comment package = Core - optional version = VERSION ; Information added by drupal.org packaging script on 2008-01-10 version = "5.6" project = "drupal" datestamp = "1200003604" loki_website/modules/forum/forum.css0000644000004100000410000000126610526261162020236 0ustar www-datawww-data/* $Id: forum.css,v 1.2 2006/11/14 06:30:10 drumm Exp $ */ #forum .description { font-size: 0.9em; margin: 0.5em; } #forum td.created, #forum td.posts, #forum td.topics, #forum td.last-reply, #forum td.replies, #forum td.pager { white-space: nowrap; } #forum td.posts, #forum td.topics, #forum td.replies, #forum td.pager { text-align: center; } .forum-topic-navigation { padding: 1em 0 0 3em; border-top: 1px solid #888; border-bottom: 1px solid #888; text-align: center; padding: 0.5em; } .forum-topic-navigation .topic-previous { text-align: right; float: left; width: 46%; } .forum-topic-navigation .topic-next { text-align: left; float: right; width: 46%; } loki_website/modules/blog/0000755000004100000410000000000010744012076016162 5ustar www-datawww-dataloki_website/modules/blog/blog.info0000644000004100000410000000050210741515024017755 0ustar www-datawww-data; $Id: blog.info,v 1.4 2006/12/17 22:02:46 dries Exp $ name = Blog description = Enables keeping easily and regularly updated user web pages or blogs. package = Core - optional version = VERSION ; Information added by drupal.org packaging script on 2008-01-10 version = "5.6" project = "drupal" datestamp = "1200003604" loki_website/modules/blog/blog.module0000644000004100000410000002425110613163510020313 0ustar www-datawww-data array( 'name' => t('Blog entry'), 'module' => 'blog', 'description' => t('A blog is a regularly updated journal or diary made up of individual posts shown in reversed chronological order. Each member of the site may create and maintain a blog.'), ) ); } /** * Implementation of hook_perm(). */ function blog_perm() { return array('edit own blog'); } /** * Implementation of hook_access(). */ function blog_access($op, $node) { global $user; if ($op == 'create') { return user_access('edit own blog') && $user->uid; } if ($op == 'update' || $op == 'delete') { if (user_access('edit own blog') && ($user->uid == $node->uid)) { return TRUE; } } } /** * Implementation of hook_user(). */ function blog_user($type, &$edit, &$user) { if ($type == 'view' && user_access('edit own blog', $user)) { $items['blog'] = array('title' => t('Blog'), 'value' => l(t('View recent blog entries'), "blog/$user->uid", array('title' => t("Read @username's latest blog entries.", array('@username' => $user->name)))), 'class' => 'blog', ); return array(t('History') => $items); } } /** * Implementation of hook_help(). */ function blog_help($section) { switch ($section) { case 'admin/help#blog': $output = '

'. t('The blog module allows registered users to maintain an online weblog (commonly known as a blog), often referred to as an online journal or diary. Blogs are made up of individual posts that are time stamped and are typically viewed by date as you would a diary. Blogs often contain links to web pages users have read and/or agree/disagree with.') .'

'; $output .= '

'. t('The blog module adds a user blogs navigation link to the site, which takes any visitor to a page that displays the most recent blog entries from all the users on the site. The navigation menu has a create a blog entry link (which takes you to a submission form) and a view personal blog link (which displays your blog entries as other people will see them). The blog module also creates a recent blog posts block that can be enabled.') .'

'; $output .= '

'. t('If a user has the ability to post blogs, then the import module (news aggregator) will display a blog-it link next to each news item in its lists. Clicking on this takes the user to the blog submission form, with the title, a link to the item, and a link to the source into the body text already in the text box, ready for the user to add a comment or explanation. This actively encourages people to add blog entries about things they see and hear elsewhere in the website and from your syndicated partner sites.') .'

'; $output .= '

'. t('For more information please read the configuration and customization handbook Blog page.', array('@blog' => 'http://drupal.org/handbook/modules/blog/')) .'

'; return $output; } } /** * Displays an RSS feed containing recent blog entries of a given user. */ function blog_feed_user($uid = 0) { global $user; if ($uid) { $account = user_load(array('uid' => $uid, 'status' => 1)); } else { $account = $user; } $result = db_query_range(db_rewrite_sql("SELECT n.nid, n.created FROM {node} n WHERE n.type = 'blog' AND n.uid = %d AND n.status = 1 ORDER BY n.created DESC"), $uid, 0, variable_get('feed_default_items', 10)); $channel['title'] = $account->name ."'s blog"; $channel['link'] = url("blog/$uid", NULL, NULL, TRUE); $channel['description'] = $term->description; node_feed($result, $channel); } /** * Displays an RSS feed containing recent blog entries of all users. */ function blog_feed_last() { $result = db_query_range(db_rewrite_sql("SELECT n.nid, n.created FROM {node} n WHERE n.type = 'blog' AND n.status = 1 ORDER BY n.created DESC"), 0, variable_get('feed_default_items', 10)); $channel['title'] = variable_get('site_name', 'Drupal') .' blogs'; $channel['link'] = url('blog', NULL, NULL, TRUE); $channel['description'] = $term->description; node_feed($result, $channel); } /** * Menu callback; displays a Drupal page containing recent blog entries. */ function blog_page($a = NULL, $b = NULL) { if (is_numeric($a)) { // $a is a user ID if ($b == 'feed') { return blog_feed_user($a); } else { return blog_page_user($a); } } else if ($a == 'feed') { return blog_feed_last(); } else { return blog_page_last(); } } /** * Displays a Drupal page containing recent blog entries of a given user. */ function blog_page_user($uid) { global $user; $account = user_load(array((is_numeric($uid) ? 'uid' : 'name') => $uid, 'status' => 1)); if ($account->uid) { drupal_set_title($title = t("@name's blog", array('@name' => $account->name))); if (($account->uid == $user->uid) && user_access('edit own blog')) { $output = '
  • '. l(t('Post new blog entry.'), "node/add/blog") .'
  • '; } else if ($account->uid == $user->uid) { $output = '
  • '. t('You are not allowed to post a new blog entry.') .'
  • '; } if ($output) { $output = '
      '. $output .'
    '; } else { $output = ''; } $result = pager_query(db_rewrite_sql("SELECT n.nid, n.sticky, n.created FROM {node} n WHERE n.type = 'blog' AND n.uid = %d AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC"), variable_get('default_nodes_main', 10), 0, NULL, $account->uid); while ($node = db_fetch_object($result)) { $output .= node_view(node_load($node->nid), 1); } $output .= theme('pager', NULL, variable_get('default_nodes_main', 10)); drupal_add_feed(url('blog/'. $account->uid .'/feed'), t('RSS - !title', array('!title' => $title))); return $output; } else { drupal_not_found(); } } /** * Displays a Drupal page containing recent blog entries of all users. */ function blog_page_last() { global $user; $output = ''; $result = pager_query(db_rewrite_sql("SELECT n.nid, n.created FROM {node} n WHERE n.type = 'blog' AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC"), variable_get('default_nodes_main', 10)); while ($node = db_fetch_object($result)) { $output .= node_view(node_load($node->nid), 1); } $output .= theme('pager', NULL, variable_get('default_nodes_main', 10)); drupal_add_feed(url('blog/feed'), t('RSS - blogs')); return $output; } /** * Implementation of hook_form(). */ function blog_form(&$node) { global $nid; $iid = $_GET['iid']; $type = node_get_types('type', $node); if (empty($node->body)) { /* ** If the user clicked a "blog it" link, we load the data from the ** database and quote it in the blog: */ if ($nid && $blog = node_load($nid)) { $node->body = ''. $blog->body .' ['. l($blog->name, "node/$nid") .']'; } if ($iid && $item = db_fetch_object(db_query('SELECT i.*, f.title as ftitle, f.link as flink FROM {aggregator_item} i, {aggregator_feed} f WHERE i.iid = %d AND i.fid = f.fid', $iid))) { $node->title = $item->title; // Note: $item->description has been validated on aggregation. $node->body = ''. check_plain($item->title) .' - '. $item->description .' ['. check_plain($item->ftitle) ."]\n"; } } $form['title'] = array('#type' => 'textfield', '#title' => check_plain($type->title_label), '#required' => TRUE, '#default_value' => $node->title, '#weight' => -5); $form['body_filter']['body'] = array('#type' => 'textarea', '#title' => check_plain($type->body_label), '#default_value' => $node->body, '#rows' => 20, '#required' => TRUE); $form['body_filter']['filter'] = filter_form($node->format); return $form; } /** * Implementation of hook_view(). */ function blog_view($node, $teaser = FALSE, $page = FALSE) { if ($page) { // Breadcrumb navigation $breadcrumb[] = array('path' => 'blog', 'title' => t('Blogs')); $breadcrumb[] = array('path' => 'blog/'. $node->uid, 'title' => t("@name's blog", array('@name' => $node->name))); $breadcrumb[] = array('path' => 'node/'. $node->nid); menu_set_location($breadcrumb); } return node_prepare($node, $teaser); } /** * Implementation of hook_link(). */ function blog_link($type, $node = NULL, $teaser = FALSE) { $links = array(); if ($type == 'node' && $node->type == 'blog') { if (arg(0) != 'blog' || arg(1) != $node->uid) { $links['blog_usernames_blog'] = array( 'title' => t("@username's blog", array('@username' => $node->name)), 'href' => "blog/$node->uid", 'attributes' => array('title' => t("Read @username's latest blog entries.", array('@username' => $node->name))) ); } } return $links; } /** * Implementation of hook_menu(). */ function blog_menu($may_cache) { global $user; $items = array(); if ($may_cache) { $items[] = array('path' => 'blog', 'title' => t('Blogs'), 'callback' => 'blog_page', 'access' => user_access('access content'), 'type' => MENU_SUGGESTED_ITEM); $items[] = array('path' => 'blog/'. $user->uid, 'title' => t('My blog'), 'access' => user_access('edit own blog'), 'type' => MENU_DYNAMIC_ITEM); } return $items; } /** * Implementation of hook_block(). * * Displays the most recent 10 blog titles. */ function blog_block($op = 'list', $delta = 0) { global $user; if ($op == 'list') { $block[0]['info'] = t('Recent blog posts'); return $block; } else if ($op == 'view') { if (user_access('access content')) { $result = db_query_range(db_rewrite_sql("SELECT n.nid, n.title, n.created FROM {node} n WHERE n.type = 'blog' AND n.status = 1 ORDER BY n.created DESC"), 0, 10); if (db_num_rows($result)) { $block['content'] = node_title_list($result); $block['content'] .= ''; $block['subject'] = t('Recent blog posts'); return $block; } } } } loki_website/modules/profile/0000755000004100000410000000000010744012100016663 5ustar www-datawww-dataloki_website/modules/profile/profile.install0000644000004100000410000000467610533044670021745 0ustar www-datawww-data'. t('The profile module allows you to define custom fields (such as country, real name, age, ...) in the user profile. This permits users of a site to share more information about themselves, and can help community-based sites to organize users around profile fields.') .'

    '; $output .= t('

    The following types of fields can be added to the user profile:

    • single-line textfield
    • multi-line textfield
    • checkbox
    • list selection
    • freeform list
    • URL
    • date
    '); $output .= '

    '. t('For more information please read the configuration and customization handbook Profile page.', array('@profile' => 'http://drupal.org/handbook/modules/profile/')) .'

    '; return $output; case 'admin/user/profile': return '

    '. t('Here you can define custom fields that users can fill in as part of their user profile (such as country, real name, age, ...).') .'

    '; } } /** * Implementation of hook_menu(). */ function profile_menu($may_cache) { $items = array(); if ($may_cache) { $items[] = array('path' => 'profile', 'title' => t('User list'), 'callback' => 'profile_browse', 'access' => user_access('access user profiles'), 'type' => MENU_SUGGESTED_ITEM); $items[] = array('path' => 'admin/user/profile', 'title' => t('Profiles'), 'description' => t('Create customizable fields for your users.'), 'callback' => 'profile_admin_overview'); $items[] = array('path' => 'admin/user/profile/add', 'title' => t('Add field'), 'callback' => 'drupal_get_form', 'callback arguments' => array('profile_field_form'), 'type' => MENU_CALLBACK); $items[] = array('path' => 'admin/user/profile/autocomplete', 'title' => t('Profile category autocomplete'), 'callback' => 'profile_admin_settings_autocomplete', 'access' => user_access('administer users'), 'type' => MENU_CALLBACK); $items[] = array('path' => 'admin/user/profile/edit', 'title' => t('Edit field'), 'callback' => 'drupal_get_form', 'callback arguments' => array('profile_field_form'), 'type' => MENU_CALLBACK); $items[] = array('path' => 'admin/user/profile/delete', 'title' => t('Delete field'), 'callback' => 'drupal_get_form', 'callback arguments' => array('profile_field_delete'), 'type' => MENU_CALLBACK); $items[] = array('path' => 'profile/autocomplete', 'title' => t('Profile autocomplete'), 'callback' => 'profile_autocomplete', 'access' => 1, 'type' => MENU_CALLBACK); } return $items; } /** * Implementation of hook_block(). */ function profile_block($op = 'list', $delta = 0, $edit = array()) { if ($op == 'list') { $blocks[0]['info'] = t('Author information'); return $blocks; } else if ($op == 'configure' && $delta == 0) { // Compile a list of fields to show $fields = array(); $result = db_query('SELECT name, title, weight, visibility FROM {profile_fields} WHERE visibility IN (%d, %d) ORDER BY weight', PROFILE_PUBLIC, PROFILE_PUBLIC_LISTINGS); while ($record = db_fetch_object($result)) { $fields[$record->name] = check_plain($record->title); } $fields['user_profile'] = t('Link to full user profile'); $form['profile_block_author_fields'] = array('#type' => 'checkboxes', '#title' => t('Profile fields to display'), '#default_value' => variable_get('profile_block_author_fields', NULL), '#options' => $fields, '#description' => t('Select which profile fields you wish to display in the block. Only fields designated as public in the profile field configuration are available.', array('@profile-admin' => url('admin/user/profile'))), ); return $form; } else if ($op == 'save' && $delta == 0) { variable_set('profile_block_author_fields', $edit['profile_block_author_fields']); } else if ($op == 'view') { if (user_access('access user profiles')) { if ((arg(0) == 'node') && is_numeric(arg(1)) && (arg(2) == NULL)) { $node = node_load(arg(1)); $account = user_load(array('uid' => $node->uid)); if ($use_fields = variable_get('profile_block_author_fields', array())) { // Compile a list of fields to show. $fields = array(); $result = db_query('SELECT name, title, type, visibility, weight FROM {profile_fields} WHERE visibility IN (%d, %d) ORDER BY weight', PROFILE_PUBLIC, PROFILE_PUBLIC_LISTINGS); while ($record = db_fetch_object($result)) { // Ensure that field is displayed only if it is among the defined block fields and, if it is private, the user has appropriate permissions. if (isset($use_fields[$record->name]) && $use_fields[$record->name]) { $fields[] = $record; } } } if ($fields) { $profile = _profile_update_user_fields($fields, $account); $output .= theme('profile_block', $account, $profile, TRUE); } if (isset($use_fields['user_profile']) && $use_fields['user_profile']) { $output .= '
    '. l(t('View full user profile'), 'user/'. $account->uid) .'
    '; } } if ($output) { $block['subject'] = t('About %name', array('%name' => $account->name)); $block['content'] = $output; return $block; } } } } /** * Implementation of hook_user(). */ function profile_user($type, &$edit, &$user, $category = NULL) { switch ($type) { case 'load': return profile_load_profile($user); case 'register': return profile_form_profile($edit, $user, $category, TRUE); case 'update': return profile_save_profile($edit, $user, $category); case 'insert': return profile_save_profile($edit, $user, $category, TRUE); case 'view': return profile_view_profile($user); case 'form': return profile_form_profile($edit, $user, $category); case 'validate': return profile_validate_profile($edit, $category); case 'categories': return profile_categories(); case 'delete': db_query('DELETE FROM {profile_values} WHERE uid = %d', $user->uid); } } /** * Menu callback: Generate a form to add/edit a user profile field. */ function profile_field_form($arg = NULL) { if (arg(3) == 'edit') { if (is_numeric($arg)) { $fid = $arg; $edit = db_fetch_array(db_query('SELECT * FROM {profile_fields} WHERE fid = %d', $fid)); if (!$edit) { drupal_not_found(); return; } drupal_set_title(t('edit %title', array('%title' => $edit['title']))); $form['fid'] = array('#type' => 'value', '#value' => $fid, ); $type = $edit['type']; } else { drupal_not_found(); return; } } else { $types = _profile_field_types(); if (!isset($types[$arg])) { drupal_not_found(); return; } $type = $arg; drupal_set_title(t('add new %type', array('%type' => $types[$type]))); $edit = array('name' => 'profile_'); $form['type'] = array('#type' => 'value', '#value' => $type); } $form['fields'] = array('#type' => 'fieldset', '#title' => t('Field settings'), ); $form['fields']['category'] = array('#type' => 'textfield', '#title' => t('Category'), '#default_value' => $edit['category'], '#autocomplete_path' => 'admin/user/profile/autocomplete', '#description' => t('The category the new field should be part of. Categories are used to group fields logically. An example category is "Personal information".'), '#required' => TRUE, ); $form['fields']['title'] = array('#type' => 'textfield', '#title' => t('Title'), '#default_value' => $edit['title'], '#description' => t('The title of the new field. The title will be shown to the user. An example title is "Favorite color".'), '#required' => TRUE, ); $form['fields']['name'] = array('#type' => 'textfield', '#title' => t('Form name'), '#default_value' => $edit['name'], '#description' => t('The name of the field. The form name is not shown to the user but used internally in the HTML code and URLs. Unless you know what you are doing, it is highly recommended that you prefix the form name with profile_ to avoid name clashes with other fields. Spaces or any other special characters except dash (-) and underscore (_) are not allowed. An example name is "profile_favorite_color" or perhaps just "profile_color".'), '#required' => TRUE, ); $form['fields']['explanation'] = array('#type' => 'textarea', '#title' => t('Explanation'), '#default_value' => $edit['explanation'], '#description' => t('An optional explanation to go with the new field. The explanation will be shown to the user.'), ); if ($type == 'selection') { $form['fields']['options'] = array('#type' => 'textarea', '#title' => t('Selection options'), '#default_value' => $edit['options'], '#description' => t('A list of all options. Put each option on a separate line. Example options are "red", "blue", "green", etc.'), ); } $form['fields']['weight'] = array('#type' => 'weight', '#title' => t('Weight'), '#default_value' => $edit['weight'], '#delta' => 5, '#description' => t('The weights define the order in which the form fields are shown. Lighter fields "float up" towards the top of the category.'), ); $form['fields']['visibility'] = array('#type' => 'radios', '#title' => t('Visibility'), '#default_value' => isset($edit['visibility']) ? $edit['visibility'] : PROFILE_PUBLIC, '#options' => array(PROFILE_HIDDEN => t('Hidden profile field, only accessible by administrators, modules and themes.'), PROFILE_PRIVATE => t('Private field, content only available to privileged users.'), PROFILE_PUBLIC => t('Public field, content shown on profile page but not used on member list pages.'), PROFILE_PUBLIC_LISTINGS => t('Public field, content shown on profile page and on member list pages.')), ); if ($type == 'selection' || $type == 'list' || $type == 'textfield') { $form['fields']['page'] = array('#type' => 'textfield', '#title' => t('Page title'), '#default_value' => $edit['page'], '#description' => t('To enable browsing this field by value, enter a title for the resulting page. The word %value will be substituted with the corresponding value. An example page title is "People whose favorite color is %value". This is only applicable for a public field.'), ); } else if ($type == 'checkbox') { $form['fields']['page'] = array('#type' => 'textfield', '#title' => t('Page title'), '#default_value' => $edit['page'], '#description' => t('To enable browsing this field by value, enter a title for the resulting page. An example page title is "People who are employed". This is only applicable for a public field.'), ); } $form['fields']['autocomplete'] = array('#type' => 'checkbox', '#title' => t('Form will auto-complete while user is typing.'), '#default_value' => $edit['autocomplete'], ); $form['fields']['required'] = array('#type' => 'checkbox', '#title' => t('The user must enter a value.'), '#default_value' => $edit['required'], ); $form['fields']['register'] = array('#type' => 'checkbox', '#title' => t('Visible in user registration form.'), '#default_value' => $edit['register'], ); $form['submit'] = array('#type' => 'submit', '#value' => t('Save field'), ); return $form; } /** * Validate profile_field_form submissions. */ function profile_field_form_validate($form_id, $form_values) { // Validate the 'field name': if (preg_match('/[^a-zA-Z0-9_-]/', $form_values['name'])) { form_set_error('name', t('The specified form name contains one or more illegal characters. Spaces or any other special characters except dash (-) and underscore (_) are not allowed.')); } if (in_array($form_values['name'], user_fields())) { form_set_error('name', t('The specified form name is reserved for use by Drupal.')); } // Validate the category: if (!$form_values['category']) { form_set_error('category', t('You must enter a category.')); } if ($form_values['category'] == 'account') { form_set_error('category', t('The specified category name is reserved for use by Drupal.')); } $args1 = array($form_values['title'], $form_values['category']); $args2 = array($form_values['name']); $query_suffix = ''; if (isset($form_values['fid'])) { $args1[] = $args2[] = $form_values['fid']; $query_suffix = ' AND fid != %d'; } if (db_result(db_query("SELECT fid FROM {profile_fields} WHERE title = '%s' AND category = '%s'". $query_suffix, $args1))) { form_set_error('title', t('The specified title is already in use.')); } if (db_result(db_query("SELECT fid FROM {profile_fields} WHERE name = '%s'". $query_suffix, $args2))) { form_set_error('name', t('The specified name is already in use.')); } } /** * Process profile_field_form submissions. */ function profile_field_form_submit($form_id, $form_values) { if (!isset($form_values['fid'])) { db_query("INSERT INTO {profile_fields} (title, name, explanation, category, type, weight, required, register, visibility, autocomplete, options, page) VALUES ('%s', '%s', '%s', '%s', '%s', %d, %d, %d, %d, %d, '%s', '%s')", $form_values['title'], $form_values['name'], $form_values['explanation'], $form_values['category'], $form_values['type'], $form_values['weight'], $form_values['required'], $form_values['register'], $form_values['visibility'], $form_values['autocomplete'], $form_values['options'], $form_values['page']); drupal_set_message(t('The field has been created.')); watchdog('profile', t('Profile field %field added under category %category.', array('%field' => $form_values['title'], '%category' => $form_values['category'])), WATCHDOG_NOTICE, l(t('view'), 'admin/user/profile')); } else { db_query("UPDATE {profile_fields} SET title = '%s', name = '%s', explanation = '%s', category = '%s', weight = %d, required = %d, register = %d, visibility = %d, autocomplete = %d, options = '%s', page = '%s' WHERE fid = %d", $form_values['title'], $form_values['name'], $form_values['explanation'], $form_values['category'], $form_values['weight'], $form_values['required'], $form_values['register'], $form_values['visibility'], $form_values['autocomplete'], $form_values['options'], $form_values['page'], $form_values['fid']); drupal_set_message(t('The field has been updated.')); } cache_clear_all(); return 'admin/user/profile'; } /** * Menu callback; deletes a field from all user profiles. */ function profile_field_delete($fid) { $field = db_fetch_object(db_query("SELECT title FROM {profile_fields} WHERE fid = %d", $fid)); if (!$field) { drupal_not_found(); return; } $form['fid'] = array('#type' => 'value', '#value' => $fid); $form['title'] = array('#type' => 'value', '#value' => $field->title); return confirm_form($form, t('Are you sure you want to delete the field %field?', array('%field' => $field->title)), 'admin/user/profile', t('This action cannot be undone. If users have entered values into this field in their profile, these entries will also be deleted. If you want to keep the user-entered data, instead of deleting the field you may wish to edit this field and change it to a hidden profile field so that it may only be accessed by administrators.', array('@edit-field' => url('admin/user/profile/edit/'. $fid))), t('Delete'), t('Cancel')); } /** * Process a field delete form submission. */ function profile_field_delete_submit($form_id, $form_values) { db_query('DELETE FROM {profile_fields} WHERE fid = %d', $form_values['fid']); db_query('DELETE FROM {profile_values} WHERE fid = %d', $form_values['fid']); cache_clear_all(); drupal_set_message(t('The field %field has been deleted.', array('%field' => $form_values['title']))); watchdog('profile', t('Profile field %field deleted.', array('%field' => $form_values['title'])), WATCHDOG_NOTICE, l(t('view'), 'admin/user/profile')); return 'admin/user/profile'; } /** * Menu callback; display a listing of all editable profile fields. */ function profile_admin_overview() { $result = db_query('SELECT title, name, type, category, fid FROM {profile_fields} ORDER BY category, weight'); $rows = array(); while ($field = db_fetch_object($result)) { $rows[] = array(check_plain($field->title), check_plain($field->name), _profile_field_types($field->type), check_plain($field->category), l(t('edit'), "admin/user/profile/edit/$field->fid"), l(t('delete'), "admin/user/profile/delete/$field->fid")); } if (count($rows) == 0) { $rows[] = array(array('data' => t('No fields defined.'), 'colspan' => '6')); } $header = array(t('Title'), t('Name'), t('Type'), t('Category'), array('data' => t('Operations'), 'colspan' => '2')); $output = theme('table', $header, $rows); $output .= '

    '. t('Add new field') .'

    '; $output .= '
      '; foreach (_profile_field_types() as $key => $value) { $output .= '
    • '. l($value, "admin/user/profile/add/$key") .'
    • '; } $output .= '
    '; return $output; } /** * Menu callback; display a list of user information. */ function profile_browse() { $name = arg(1); list(, , $value) = explode('/', $_GET['q'], 3); $field = db_fetch_object(db_query("SELECT DISTINCT(fid), type, title, page, visibility FROM {profile_fields} WHERE name = '%s'", $name)); if ($name && $field->fid) { // Only allow browsing of fields that have a page title set. if (empty($field->page)) { drupal_not_found(); return; } // Do not allow browsing of private and hidden fields by non-admins. if (!user_access('administer users') && ($field->visibility == PROFILE_PRIVATE || $field->visibility == PROFILE_HIDDEN)) { drupal_access_denied(); return; } // Compile a list of fields to show. $fields = array(); $result = db_query('SELECT name, title, type, weight, page FROM {profile_fields} WHERE fid != %d AND visibility = %d ORDER BY weight', $field->fid, PROFILE_PUBLIC_LISTINGS); while ($record = db_fetch_object($result)) { $fields[] = $record; } // Determine what query to use: $arguments = array($field->fid); switch ($field->type) { case 'checkbox': $query = 'v.value = 1'; break; case 'textfield': case 'selection': $query = "v.value = '%s'"; $arguments[] = $value; break; case 'list': $query = "v.value LIKE '%%%s%%'"; $arguments[] = $value; break; default: drupal_not_found(); return; } // Extract the affected users: $result = pager_query("SELECT u.uid, u.access FROM {users} u INNER JOIN {profile_values} v ON u.uid = v.uid WHERE v.fid = %d AND $query AND u.access != 0 AND u.status != 0 ORDER BY u.access DESC", 20, 0, NULL, $arguments); $output = '
    '; while ($account = db_fetch_object($result)) { $account = user_load(array('uid' => $account->uid)); $profile = _profile_update_user_fields($fields, $account); $output .= theme('profile_listing', $account, $profile); } $output .= theme('pager', NULL, 20); if ($field->type == 'selection' || $field->type == 'list' || $field->type == 'textfield') { $title = strtr(check_plain($field->page), array('%value' => theme('placeholder', $value))); } else { $title = check_plain($field->page); } $output .= '
    '; drupal_set_title($title); return $output; } else if ($name && !$field->fid) { drupal_not_found(); } else { // Compile a list of fields to show. $fields = array(); $result = db_query('SELECT name, title, type, weight, page FROM {profile_fields} WHERE visibility = %d ORDER BY category, weight', PROFILE_PUBLIC_LISTINGS); while ($record = db_fetch_object($result)) { $fields[] = $record; } // Extract the affected users: $result = pager_query('SELECT uid, access FROM {users} WHERE uid > 0 AND status != 0 AND access != 0 ORDER BY access DESC', 20, 0, NULL); $output = '
    '; while ($account = db_fetch_object($result)) { $account = user_load(array('uid' => $account->uid)); $profile = _profile_update_user_fields($fields, $account); $output .= theme('profile_listing', $account, $profile); } $output .= '
    '; $output .= theme('pager', NULL, 20); drupal_set_title(t('User list')); return $output; } } function profile_load_profile(&$user) { $result = db_query('SELECT f.name, f.type, v.value FROM {profile_fields} f INNER JOIN {profile_values} v ON f.fid = v.fid WHERE uid = %d', $user->uid); while ($field = db_fetch_object($result)) { if (empty($user->{$field->name})) { $user->{$field->name} = _profile_field_serialize($field->type) ? unserialize($field->value) : $field->value; } } } function profile_save_profile(&$edit, &$user, $category, $register = FALSE) { $result = _profile_get_fields($category, $register); while ($field = db_fetch_object($result)) { if (_profile_field_serialize($field->type)) { $edit[$field->name] = serialize($edit[$field->name]); } db_query("DELETE FROM {profile_values} WHERE fid = %d AND uid = %d", $field->fid, $user->uid); db_query("INSERT INTO {profile_values} (fid, uid, value) VALUES (%d, %d, '%s')", $field->fid, $user->uid, $edit[$field->name]); // Mark field as handled (prevents saving to user->data). $edit[$field->name] = NULL; } } function profile_view_field($user, $field) { // Only allow browsing of private fields for admins, if browsing is enabled, // and if a user has permission to view profiles. Note that this check is // necessary because a user may always see their own profile. $browse = user_access('access user profiles') && (user_access('administer users') || $field->visibility != PROFILE_PRIVATE) && !empty($field->page); if ($value = $user->{$field->name}) { switch ($field->type) { case 'textarea': return check_markup($value); case 'textfield': case 'selection': return $browse ? l($value, 'profile/'. $field->name .'/'. $value) : check_plain($value); case 'checkbox': return $browse ? l($field->title, 'profile/'. $field->name) : check_plain($field->title); case 'url': return ''. check_plain($value) .''; case 'date': $format = substr(variable_get('date_format_short', 'm/d/Y - H:i'), 0, 5); // Note: Avoid PHP's date() because it does not handle dates before // 1970 on Windows. This would make the date field useless for e.g. // birthdays. $replace = array('d' => sprintf('%02d', $value['day']), 'j' => $value['day'], 'm' => sprintf('%02d', $value['month']), 'M' => map_month($value['month']), 'Y' => $value['year'], 'H:i' => NULL, 'g:ia' => NULL); return strtr($format, $replace); case 'list': $values = split("[,\n\r]", $value); $fields = array(); foreach ($values as $value) { if ($value = trim($value)) { $fields[] = $browse ? l($value, 'profile/'. $field->name .'/'. $value) : check_plain($value); } } return implode(', ', $fields); } } } function profile_view_profile($user) { profile_load_profile($user); // Show private fields to administrators and people viewing their own account. if (user_access('administer users') || $GLOBALS['user']->uid == $user->uid) { $result = db_query('SELECT * FROM {profile_fields} WHERE visibility != %d ORDER BY category, weight', PROFILE_HIDDEN); } else { $result = db_query('SELECT * FROM {profile_fields} WHERE visibility != %d AND visibility != %d ORDER BY category, weight', PROFILE_PRIVATE, PROFILE_HIDDEN); } while ($field = db_fetch_object($result)) { if ($value = profile_view_field($user, $field)) { $title = ($field->type != 'checkbox') ? check_plain($field->title) : NULL; $item = array('title' => $title, 'value' => $value, 'class' => $field->name, ); $fields[$field->category][$field->name] = $item; } } return $fields; } function _profile_form_explanation($field) { $output = $field->explanation; if ($field->type == 'list') { $output .= ' '. t('Put each item on a separate line or separate them by commas. No HTML allowed.'); } if ($field->visibility == PROFILE_PRIVATE) { $output .= ' '. t('The content of this field is kept private and will not be shown publicly.'); } return $output; } function profile_form_profile($edit, $user, $category, $register = FALSE) { $result = _profile_get_fields($category, $register); $w = 1; while ($field = db_fetch_object($result)) { $category = $field->category; if (!isset($fields[$category])) { $fields[$category] = array('#type' => 'fieldset', '#title' => check_plain($category), '#weight' => $w++); } switch ($field->type) { case 'textfield': case 'url': $fields[$category][$field->name] = array('#type' => 'textfield', '#title' => check_plain($field->title), '#default_value' => $edit[$field->name], '#maxlength' => 255, '#description' => _profile_form_explanation($field), '#required' => $field->required, ); if ($field->autocomplete) { $fields[$category][$field->name]['#autocomplete_path'] = "profile/autocomplete/". $field->fid; } break; case 'textarea': $fields[$category][$field->name] = array('#type' => 'textarea', '#title' => check_plain($field->title), '#default_value' => $edit[$field->name], '#description' => _profile_form_explanation($field), '#required' => $field->required, ); break; case 'list': $fields[$category][$field->name] = array('#type' => 'textarea', '#title' => check_plain($field->title), '#default_value' => $edit[$field->name], '#description' => _profile_form_explanation($field), '#required' => $field->required, ); break; case 'checkbox': $fields[$category][$field->name] = array('#type' => 'checkbox', '#title' => check_plain($field->title), '#default_value' => $edit[$field->name], '#description' => _profile_form_explanation($field), '#required' => $field->required, ); break; case 'selection': $options = $field->required ? array() : array('--'); $lines = split("[,\n\r]", $field->options); foreach ($lines as $line) { if ($line = trim($line)) { $options[$line] = $line; } } $fields[$category][$field->name] = array('#type' => 'select', '#title' => check_plain($field->title), '#default_value' => $edit[$field->name], '#options' => $options, '#description' => _profile_form_explanation($field), '#required' => $field->required, ); break; case 'date': $fields[$category][$field->name] = array('#type' => 'date', '#title' => check_plain($field->title), '#default_value' => $edit[$field->name], '#description' => _profile_form_explanation($field), '#required' => $field->required, ); break; } } return $fields; } /** * Callback to allow autocomplete of profile text fields. */ function profile_autocomplete($field, $string) { if (db_result(db_query("SELECT COUNT(*) FROM {profile_fields} WHERE fid = %d AND autocomplete = 1", $field))) { $matches = array(); $result = db_query_range("SELECT value FROM {profile_values} WHERE fid = %d AND LOWER(value) LIKE LOWER('%s%%') GROUP BY value ORDER BY value ASC", $field, $string, 0, 10); while ($data = db_fetch_object($result)) { $matches[$data->value] = check_plain($data->value); } print drupal_to_js($matches); } exit(); } /** * Helper function: update an array of user fields by calling profile_view_field */ function _profile_update_user_fields($fields, $account) { foreach ($fields as $key => $field) { $fields[$key]->value = profile_view_field($account, $field); } return $fields; } function profile_validate_profile($edit, $category) { $result = _profile_get_fields($category); while ($field = db_fetch_object($result)) { if ($edit[$field->name]) { if ($field->type == 'url') { if (!valid_url($edit[$field->name], TRUE)) { form_set_error($field->name, t('The value provided for %field is not a valid URL.', array('%field' => $field->title))); } } } else if ($field->required && !user_access('administer users')) { form_set_error($field->name, t('The field %field is required.', array('%field' => $field->title))); } } return $edit; } function profile_categories() { $result = db_query("SELECT DISTINCT(category) FROM {profile_fields}"); while ($category = db_fetch_object($result)) { $data[] = array('name' => $category->category, 'title' => $category->category, 'weight' => 3); } return $data; } function theme_profile_block($account, $fields = array()) { $output .= theme('user_picture', $account); foreach ($fields as $field) { if ($field->value) { if ($field->type == 'checkbox') { $output .= "

    $field->value

    \n"; } else { $output .= '

    '. check_plain($field->title) ."
    $field->value

    \n"; } } } return $output; } function theme_profile_listing($account, $fields = array()) { $output = "
    \n"; $output .= theme('user_picture', $account); $output .= '
    '. theme('username', $account) ."
    \n"; foreach ($fields as $field) { if ($field->value) { $output .= "
    $field->value
    \n"; } } $output .= "
    \n"; return $output; } function _profile_field_types($type = NULL) { $types = array('textfield' => t('single-line textfield'), 'textarea' => t('multi-line textfield'), 'checkbox' => t('checkbox'), 'selection' => t('list selection'), 'list' => t('freeform list'), 'url' => t('URL'), 'date' => t('date')); return isset($type) ? $types[$type] : $types; } function _profile_field_serialize($type = NULL) { return $type == 'date'; } function _profile_get_fields($category, $register = FALSE) { $args = array(); $sql = 'SELECT * FROM {profile_fields} WHERE '; $filters = array(); if ($register) { $filters[] = 'register = 1'; } else { // Use LOWER('%s') instead of PHP's strtolower() to avoid UTF-8 conversion issues. $filters[] = "LOWER(category) = LOWER('%s')"; $args[] = $category; } if (!user_access('administer users')) { $filters[] = 'visibility != %d'; $args[] = PROFILE_HIDDEN; } $sql .= implode(' AND ', $filters); $sql .= ' ORDER BY category, weight'; return db_query($sql, $args); } /** * Retrieve a pipe delimited string of autocomplete suggestions for profile categories */ function profile_admin_settings_autocomplete($string) { $matches = array(); $result = db_query_range("SELECT category FROM {profile_fields} WHERE LOWER(category) LIKE LOWER('%s%%')", $string, 0, 10); while ($data = db_fetch_object($result)) { $matches[$data->category] = check_plain($data->category); } print drupal_to_js($matches); exit(); } loki_website/modules/locale/0000755000004100000410000000000010744012100016462 5ustar www-datawww-dataloki_website/modules/locale/locale.info0000644000004100000410000000051710741515024020613 0ustar www-datawww-data; $Id: locale.info,v 1.3 2006/11/21 20:55:34 dries Exp $ name = Locale description = Enables the translation of the user interface to languages other than English. package = Core - optional version = VERSION ; Information added by drupal.org packaging script on 2008-01-10 version = "5.6" project = "drupal" datestamp = "1200003604" loki_website/modules/locale/locale.module0000644000004100000410000004235310544470440021154 0ustar www-datawww-data'. t('The locale module allows you to present your Drupal site in a language other than the default English. You can use it to set up a multi-lingual web site or replace given built-in text with text which has been customized for your site. Whenever the locale module encounters text which needs to be displayed, it tries to translate it into the currently selected language. If a translation is not available, then the string is remembered, so you can look up untranslated strings easily.') .'

    '; $output .= '

    '. t('The locale module provides two options for providing translations. The first is the integrated web interface, via which you can search for untranslated strings, and specify their translations. An easier and less time-consuming method is to import existing translations for your language. These translations are available as GNU gettext Portable Object files (.po files for short). Translations for many languages are available for download from the translation page.') .'

    '; $output .= '

    '. t("If an existing translation does not meet your needs, the .po files are easily edited with special editing tools. The locale module's import feature allows you to add strings from such files into your site's database. The export functionality enables you to share your translations with others, generating Portable Object files from your site strings.") .'

    '; $output .= '

    '. t('For more information please read the configuration and customization handbook Locale page.', array('@locale' => 'http://drupal.org/handbook/modules/locale/')) .'

    '; return $output; case 'admin/settings/locale': case 'admin/settings/locale/language/overview': return t("

    Drupal provides support for the translation of its interface text into different languages. This page provides an overview of the installed languages. You can add a language on the add language page, or directly by importing a translation. If multiple languages are enabled, registered users will be able to set their preferred language. The site default will be used for anonymous visitors and for users without their own settings.

    Drupal interface translations may be added or extended by several courses: by importing an existing translation, by translating everything from scratch, or by a combination of these approaches.

    ", array("@search" => url("admin/settings/locale/string/search"), "@import" => url("admin/settings/locale/language/import"), "@add-language" => url("admin/settings/locale/language/add"))); case 'admin/settings/locale/language/add': return '

    '. t("You need to add all languages in which you would like to display the site interface. If you can't find the desired language in the quick-add dropdown, then you will need to provide the proper language code yourself. The language code may be used to negotiate with browsers and to present flags, etc., so it is important to pick a code that is standardised for the desired language. You can also add a language by importing a translation.", array("@import" => url("admin/settings/locale/language/import"))) .'

    '; case 'admin/settings/locale/language/import': return '

    '. t("This page allows you to import a translation provided in the gettext Portable Object (.po) format. The easiest way to get your site translated is to obtain an existing Drupal translation and to import it. You can find existing translations on the Drupal translation page. Note that importing a translation file might take a while.", array('@url' => 'http://drupal.org/project/translations')) .'

    '; case 'admin/settings/locale/language/export': return '

    '. t("This page allows you to export Drupal strings. The first option is to export a translation so it can be shared. The second option generates a translation template, which contains all Drupal strings, but without their translations. You can use this template to start a new translation using various software packages designed for this task.") .'

    '; case 'admin/settings/locale/string/search': return '

    '. t("It is often convenient to get the strings from your setup on the export page, and use a desktop Gettext translation editor to edit the translations. On this page you can search in the translated and untranslated strings, and the default English texts provided by Drupal.", array("@export" => url("admin/settings/locale/language/export"))) .'

    '; } } /** * Implementation of hook_menu(). */ function locale_menu($may_cache) { $items = array(); $access = user_access('administer locales'); if ($may_cache) { // Main admin menu item $items[] = array('path' => 'admin/settings/locale', 'title' => t('Localization'), 'description' => t('Configure site localization and user interface translation.'), 'callback' => 'locale_admin_manage', 'access' => $access); // Top level tabs $items[] = array('path' => 'admin/settings/locale/language', 'title' => t('Manage languages'), 'access' => $access, 'weight' => -10, 'type' => MENU_DEFAULT_LOCAL_TASK); $items[] = array('path' => 'admin/settings/locale/string/search', 'title' => t('Manage strings'), 'callback' => 'locale_string_search', 'access' => $access, 'weight' => 10, 'type' => MENU_LOCAL_TASK); // Manage languages subtabs $items[] = array('path' => 'admin/settings/locale/language/overview', 'title' => t('List'), 'callback' => 'locale_admin_manage', 'access' => $access, 'weight' => 0, 'type' => MENU_DEFAULT_LOCAL_TASK); $items[] = array('path' => 'admin/settings/locale/language/add', 'title' => t('Add language'), 'callback' => 'locale_admin_manage_add', 'access' => $access, 'weight' => 5, 'type' => MENU_LOCAL_TASK); $items[] = array('path' => 'admin/settings/locale/language/import', 'title' => t('Import'), 'callback' => 'locale_admin_import', 'access' => $access, 'weight' => 10, 'type' => MENU_LOCAL_TASK); $items[] = array('path' => 'admin/settings/locale/language/export', 'title' => t('Export'), 'callback' => 'locale_admin_export', 'access' => $access, 'weight' => 20, 'type' => MENU_LOCAL_TASK); // Language related callbacks $items[] = array('path' => 'admin/settings/locale/language/delete', 'title' => t('Confirm'), 'callback' => 'drupal_get_form', 'callback arguments' => array('locale_admin_manage_delete_form'), 'access' => $access, 'type' => MENU_CALLBACK); } else { if (is_numeric(arg(5))) { // String related callbacks $items[] = array('path' => 'admin/settings/locale/string/edit/'. arg(5), 'title' => t('Edit string'), 'callback' => 'drupal_get_form', 'callback arguments' => array('locale_admin_string_edit', arg(5)), 'access' => $access, 'type' => MENU_CALLBACK); $items[] = array('path' => 'admin/settings/locale/string/delete/'. arg(5), 'title' => t('Delete string'), 'callback' => 'locale_admin_string_delete', 'callback arguments' => array(arg(5)), 'access' => $access, 'type' => MENU_CALLBACK); } } return $items; } /** * Implementation of hook_perm(). */ function locale_perm() { return array('administer locales'); } /** * Implementation of hook_user(). */ function locale_user($type, $edit, &$user, $category = NULL) { $languages = locale_supported_languages(); if ($type == 'form' && $category == 'account' && count($languages['name']) > 1) { if ($user->language == '') { $user->language = key($languages['name']); } $languages['name'] = array_map('check_plain', array_map('t', $languages['name'])); $form['locale'] = array('#type' => 'fieldset', '#title' => t('Interface language settings'), '#weight' => 1, ); $form['locale']['language'] = array('#type' => 'radios', '#title' => t('Language'), '#default_value' => $user->language, '#options' => $languages['name'], '#description' => t('Selecting a different locale will change the interface language of the site.'), ); return $form; } } // --------------------------------------------------------------------------------- // Locale core functionality (needed on all page loads) /** * Provides interface translation services. * * This function is called from t() to translate a string if needed. */ function locale($string) { global $locale; static $locale_t; // Store database cached translations in a static var. if (!isset($locale_t)) { $cache = cache_get("locale:$locale", 'cache'); if (!$cache) { locale_refresh_cache(); $cache = cache_get("locale:$locale", 'cache'); } $locale_t = unserialize($cache->data); } // We have the translation cached (if it is TRUE, then there is no // translation, so there is no point in checking the database) if (isset($locale_t[$string])) { $string = ($locale_t[$string] === TRUE ? $string : $locale_t[$string]); } // We do not have this translation cached, so get it from the DB. else { $result = db_query("SELECT s.lid, t.translation FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid WHERE s.source = '%s' AND t.locale = '%s'", $string, $locale); // Translation found if ($trans = db_fetch_object($result)) { if (!empty($trans->translation)) { $locale_t[$string] = $trans->translation; $string = $trans->translation; } } // Either we have no such source string, or no translation else { $result = db_query("SELECT lid, source FROM {locales_source} WHERE source = '%s'", $string); // We have no such translation if ($obj = db_fetch_object($result)) { if ($locale) { db_query("INSERT INTO {locales_target} (lid, locale, translation) VALUES (%d, '%s', '')", $obj->lid, $locale); } } // We have no such source string else { db_query("INSERT INTO {locales_source} (location, source) VALUES ('%s', '%s')", request_uri(), $string); if ($locale) { $lid = db_fetch_object(db_query("SELECT lid FROM {locales_source} WHERE source = '%s'", $string)); db_query("INSERT INTO {locales_target} (lid, locale, translation) VALUES (%d, '%s', '')", $lid->lid, $locale); } } // Clear locale cache in DB cache_clear_all("locale:$locale", 'cache'); } } return $string; } /** * Refreshes database stored cache of translations. * * We only store short strings to improve performance and consume less memory. */ function locale_refresh_cache() { $languages = locale_supported_languages(); foreach (array_keys($languages['name']) as $locale) { $result = db_query("SELECT s.source, t.translation, t.locale FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid WHERE t.locale = '%s' AND LENGTH(s.source) < 75", $locale); $t = array(); while ($data = db_fetch_object($result)) { $t[$data->source] = (empty($data->translation) ? TRUE : $data->translation); } cache_set("locale:$locale", 'cache', serialize($t)); } } /** * Returns list of languages supported on this site. * * @param $reset Refresh cached language list. * @param $getall Return all languages (even disabled ones) */ function locale_supported_languages($reset = FALSE, $getall = FALSE) { static $enabled = NULL; static $all = NULL; if ($reset) { unset($enabled); unset($all); } if (is_null($enabled)) { $enabled = $all = array(); $all['name'] = $all['formula'] = $enabled['name'] = $enabled['formula'] = array(); $result = db_query('SELECT locale, name, formula, enabled FROM {locales_meta} ORDER BY isdefault DESC, enabled DESC, name ASC'); while ($row = db_fetch_object($result)) { $all['name'][$row->locale] = $row->name; $all['formula'][$row->locale] = $row->formula; if ($row->enabled) { $enabled['name'][$row->locale] = $row->name; $enabled['formula'][$row->locale] = $row->formula; } } } return $getall ? $all : $enabled; } /** * Returns plural form index for a specific number. * * The index is computed from the formula of this language. */ function locale_get_plural($count) { global $locale; static $locale_formula, $plurals = array(); if (!isset($plurals[$count])) { if (!isset($locale_formula)) { $languages = locale_supported_languages(); $locale_formula = $languages['formula'][$locale]; } if ($locale_formula) { $n = $count; $plurals[$count] = @eval("return intval($locale_formula);"); return $plurals[$count]; } else { $plurals[$count] = -1; return -1; } } return $plurals[$count]; } // --------------------------------------------------------------------------------- // Language management functionality (administration only) /** * Page handler for the language management screen. */ function locale_admin_manage() { include_once './includes/locale.inc'; return drupal_get_form('_locale_admin_manage_screen'); } /** * User interface for the language deletion confirmation screen. */ function locale_admin_manage_delete_form($langcode) { include_once './includes/locale.inc'; // Do not allow deletion of English locale. if ($langcode == 'en') { drupal_set_message(t('The English locale cannot be deleted.')); drupal_goto('admin/settings/locale/language/overview'); } // For other locales, warn user that data loss is ahead. $languages = locale_supported_languages(FALSE, TRUE); if (!isset($languages['name'][$langcode])) { drupal_not_found(); } else { $form['langcode'] = array('#type' => 'value', '#value' => $langcode); return confirm_form($form, t('Are you sure you want to delete the language %name?', array('%name' => t($languages['name'][$langcode]))), 'admin/settings/locale/language/overview', t('Deleting a language will remove all data associated with it. This action cannot be undone.'), t('Delete'), t('Cancel')); } } /** * Process language deletion submissions. */ function locale_admin_manage_delete_form_submit($form_id, $form_values) { $languages = locale_supported_languages(FALSE, TRUE); if (isset($languages['name'][$form_values['langcode']])) { db_query("DELETE FROM {locales_meta} WHERE locale = '%s'", $form_values['langcode']); db_query("DELETE FROM {locales_target} WHERE locale = '%s'", $form_values['langcode']); $message = t('The language %locale has been removed.', array('%locale' => t($languages['name'][$form_values['langcode']]))); drupal_set_message($message); watchdog('locale', $message); } // Changing the locale settings impacts the interface: cache_clear_all('*', 'cache_menu', TRUE); cache_clear_all('*', 'cache_page', TRUE); return 'admin/settings/locale/language/overview'; } /** * Page handler for the language addition screen */ function locale_admin_manage_add() { include_once './includes/locale.inc'; return _locale_admin_manage_add_screen(); } // --------------------------------------------------------------------------------- // Gettext Portable Object import functionality (administration only) /** * Page handler for the translation import screen */ function locale_admin_import() { include_once './includes/locale.inc'; return drupal_get_form('_locale_admin_import'); } // --------------------------------------------------------------------------------- // Gettext Portable Object export functionality (administration only) /** * Page handler for the translation export screen */ function locale_admin_export() { include_once './includes/locale.inc'; return _locale_admin_export_screen(); } // --------------------------------------------------------------------------------- // String search and editing functionality (administration only) /** * Page handler for the string search. */ function locale_string_search() { include_once './includes/locale.inc'; $output = _locale_string_seek(); $output .= drupal_get_form('_locale_string_seek_form'); return $output; } /** * Display the string edit form. */ function locale_admin_string_edit($lid) { include_once './includes/locale.inc'; return _locale_string_edit($lid); } /** * Process the string edit form. */ function locale_admin_string_edit_submit($form_id, $form_values) { include_once './includes/locale.inc'; return _locale_string_edit_submit($form_id, $form_values); } /** * Delete a string. */ function locale_admin_string_delete($lid) { include_once './includes/locale.inc'; _locale_string_delete($lid); } loki_website/modules/locale/locale.install0000644000004100000410000000605310526260070021326 0ustar www-datawww-data'. t('The search module adds the ability to search for content by keywords. Search is often the only practical way to find content on a large site. Search is useful for finding users and posts by searching on keywords.') .'

    '; $output .= '

    '. t('The search engine works by maintaining an index of the words in your site\'s content. It indexes the posts and users. You can adjust the settings to tweak the indexing behaviour. Note that the search requires cron to be set up correctly. The index percentage sets the maximum amount of items that will be indexed in one cron run. Set this number lower if your cron is timing out or if PHP is running out of memory.') .'

    '; $output .= '

    '. t('For more information please read the configuration and customization handbook Search page.', array('@search' => 'http://drupal.org/handbook/modules/search/')) .'

    '; return $output; case 'admin/settings/search': return '

    '. t('The search engine works by maintaining an index of the words in your site\'s content. You can adjust the settings below to tweak the indexing behaviour. Note that the search requires cron to be set up correctly.') .'

    '; case 'search#noresults': return t('
    • Check if your spelling is correct.
    • Remove quotes around phrases to match each word individually: "blue smurf" will match less than blue smurf.
    • Consider loosening your query with OR: blue smurf will match less than blue OR smurf.
    '); } } /** * Implementation of hook_perm(). */ function search_perm() { return array('search content', 'use advanced search', 'administer search'); } /** * Implementation of hook_block(). */ function search_block($op = 'list', $delta = 0) { if ($op == 'list') { $blocks[0]['info'] = t('Search form'); return $blocks; } else if ($op == 'view' && user_access('search content')) { $block['content'] = drupal_get_form('search_block_form'); $block['subject'] = t('Search'); return $block; } } /** * Implementation of hook_menu(). */ function search_menu($may_cache) { $items = array(); if ($may_cache) { $items[] = array('path' => 'search', 'title' => t('Search'), 'callback' => 'search_view', 'access' => user_access('search content'), 'type' => MENU_SUGGESTED_ITEM); $items[] = array('path' => 'admin/settings/search', 'title' => t('Search settings'), 'description' => t('Configure relevance settings for search and other indexing options'), 'callback' => 'drupal_get_form', 'callback arguments' => array('search_admin_settings'), 'access' => user_access('administer search'), 'type' => MENU_NORMAL_ITEM); $items[] = array('path' => 'admin/settings/search/wipe', 'title' => t('Clear index'), 'callback' => 'drupal_get_form', 'callback arguments' => array('search_wipe_confirm'), 'access' => user_access('administer search'), 'type' => MENU_CALLBACK); $items[] = array('path' => 'admin/logs/search', 'title' => t('Top search phrases'), 'description' => t('View most popular search phrases.'), 'callback' => 'watchdog_top', 'callback arguments' => array('search')); } else if (arg(0) == 'search') { // To remember the user's search keywords when switching across tabs, // we dynamically add the keywords to the search tabs' paths. $keys = search_get_keys(); $keys = strlen($keys) ? '/'. $keys : ''; foreach (module_list() as $name) { if (module_hook($name, 'search') && $title = module_invoke($name, 'search', 'name')) { $items[] = array('path' => 'search/'. $name . $keys, 'title' => $title, 'callback' => 'search_view', 'access' => user_access('search content'), 'type' => MENU_LOCAL_TASK); } } } return $items; } /** * Validate callback. */ function search_admin_settings_validate($form_id, $form_values) { if ($form_values['op'] == t('Re-index site')) { drupal_goto('admin/settings/search/wipe'); } // If these settings change, the index needs to be rebuilt. if ((variable_get('minimum_word_size', 3) != $form_values['minimum_word_size']) || (variable_get('overlap_cjk', TRUE) != $form_values['overlap_cjk'])) { drupal_set_message(t('The index will be rebuilt.')); search_wipe(); } } /** * Menu callback; displays the search module settings page. */ function search_admin_settings() { // Collect some stats $remaining = 0; $total = 0; foreach (module_list() as $module) { if (module_hook($module, 'search')) { $status = module_invoke($module, 'search', 'status'); $remaining += $status['remaining']; $total += $status['total']; } } $count = format_plural($remaining, 'There is 1 item left to index.', 'There are @count items left to index.'); $percentage = ((int)min(100, 100 * ($total - $remaining) / max(1, $total))) .'%'; $status = '

    '. t('%percentage of the site has been indexed.', array('%percentage' => $percentage)) .' '. $count .'

    '; $form['status'] = array('#type' => 'fieldset', '#title' => t('Indexing status')); $form['status']['status'] = array('#value' => $status); $form['status']['wipe'] = array('#type' => 'submit', '#value' => t('Re-index site')); $items = drupal_map_assoc(array(10, 20, 50, 100, 200, 500)); // Indexing throttle: $form['indexing_throttle'] = array('#type' => 'fieldset', '#title' => t('Indexing throttle')); $form['indexing_throttle']['search_cron_limit'] = array('#type' => 'select', '#title' => t('Items to index per cron run'), '#default_value' => variable_get('search_cron_limit', 100), '#options' => $items, '#description' => t('The maximum amount of items that will be indexed in one cron run. Set this number lower if your cron is timing out or if PHP is running out of memory.')); // Indexing settings: $form['indexing_settings'] = array('#type' => 'fieldset', '#title' => t('Indexing settings')); $form['indexing_settings']['info'] = array('#value' => ''. t('

    Changing the settings below will cause the site index to be rebuilt. The search index is not cleared but systematically updated to reflect the new settings. Searching will continue to work but new content won\'t be indexed until all existing content has been re-indexed.

    The default settings should be appropriate for the majority of sites.

    ') .'
    '); $form['indexing_settings']['minimum_word_size'] = array('#type' => 'textfield', '#title' => t('Minimum word length to index'), '#default_value' => variable_get('minimum_word_size', 3), '#size' => 5, '#maxlength' => 3, '#description' => t('The number of characters a word has to be to be indexed. A lower setting means better search result ranking, but also a larger database. Each search query must contain at least one keyword that is this size (or longer).')); $form['indexing_settings']['overlap_cjk'] = array('#type' => 'checkbox', '#title' => t('Simple CJK handling'), '#default_value' => variable_get('overlap_cjk', TRUE), '#description' => t('Whether to apply a simple Chinese/Japanese/Korean tokenizer based on overlapping sequences. Turn this off if you want to use an external preprocessor for this instead. Does not affect other languages.')); // Per module settings $form = array_merge($form, module_invoke_all('search', 'admin')); return system_settings_form($form); } /** * Menu callback: confirm wiping of the index. */ function search_wipe_confirm() { return confirm_form(array(), t('Are you sure you want to re-index the site?'), 'admin/settings/search', t(' The search index is not cleared but systematically updated to reflect the new settings. Searching will continue to work but new content won\'t be indexed until all existing content has been re-indexed. This action cannot be undone.'), t('Re-index site'), t('Cancel')); } /** * Handler for wipe confirmation */ function search_wipe_confirm_submit($form_id, &$form) { if ($form['confirm']) { search_wipe(); drupal_set_message(t('The index will be rebuilt.')); return 'admin/settings/search'; } } /** * Wipes a part of or the entire search index. * * @param $sid * (optional) The SID of the item to wipe. If specified, $type must be passed * too. * @param $type * (optional) The type of item to wipe. */ function search_wipe($sid = NULL, $type = NULL, $reindex = FALSE) { if ($type == NULL && $sid == NULL) { module_invoke_all('search', 'reset'); } else { db_query("DELETE FROM {search_dataset} WHERE sid = %d AND type = '%s'", $sid, $type); db_query("DELETE FROM {search_index} WHERE fromsid = %d AND fromtype = '%s'", $sid, $type); // When re-indexing, keep link references db_query("DELETE FROM {search_index} WHERE sid = %d AND type = '%s'". ($reindex ? " AND fromsid = 0" : ''), $sid, $type); } } /** * Marks a word as dirty (or retrieves the list of dirty words). This is used * during indexing (cron). Words which are dirty have outdated total counts in * the search_total table, and need to be recounted. */ function search_dirty($word = NULL) { static $dirty = array(); if ($word !== NULL) { $dirty[$word] = TRUE; } else { return $dirty; } } /** * Implementation of hook_cron(). * * Fires hook_update_index() in all modules and cleans up dirty words (see * search_dirty). */ function search_cron() { // We register a shutdown function to ensure that search_total is always up // to date. register_shutdown_function('search_update_totals'); // Update word index foreach (module_list() as $module) { module_invoke($module, 'update_index'); } } /** * This function is called on shutdown to ensure that search_total is always * up to date (even if cron times out or otherwise fails). */ function search_update_totals() { // Update word IDF (Inverse Document Frequency) counts for new/changed words foreach (search_dirty() as $word => $dummy) { // Get total count $total = db_result(db_query("SELECT SUM(score) FROM {search_index} WHERE word = '%s'", $word)); // Apply Zipf's law to equalize the probability distribution $total = log10(1 + 1/(max(1, $total))); db_query("UPDATE {search_total} SET count = %f WHERE word = '%s'", $total, $word); if (!db_affected_rows()) { db_query("INSERT INTO {search_total} (word, count) VALUES ('%s', %f)", $word, $total); } } // Find words that were deleted from search_index, but are still in // search_total. We use a LEFT JOIN between the two tables and keep only the // rows which fail to join. $result = db_query("SELECT t.word AS realword, i.word FROM {search_total} t LEFT JOIN {search_index} i ON t.word = i.word WHERE i.word IS NULL"); while ($word = db_fetch_object($result)) { db_query("DELETE FROM {search_total} WHERE word = '%s'", $word->realword); } } /** * Simplifies a string according to indexing rules. */ function search_simplify($text) { // Decode entities to UTF-8 $text = decode_entities($text); // Lowercase $text = drupal_strtolower($text); // Call an external processor for word handling. search_preprocess($text); // Simple CJK handling if (variable_get('overlap_cjk', TRUE)) { $text = preg_replace_callback('/['. PREG_CLASS_CJK .']+/u', 'search_expand_cjk', $text); } // To improve searching for numerical data such as dates, IP addresses // or version numbers, we consider a group of numerical characters // separated only by punctuation characters to be one piece. // This also means that searching for e.g. '20/03/1984' also returns // results with '20-03-1984' in them. // Readable regexp: ([number]+)[punctuation]+(?=[number]) $text = preg_replace('/(['. PREG_CLASS_NUMBERS .']+)['. PREG_CLASS_PUNCTUATION .']+(?=['. PREG_CLASS_NUMBERS .'])/u', '\1', $text); // The dot, underscore and dash are simply removed. This allows meaningful // search behaviour with acronyms and URLs. $text = preg_replace('/[._-]+/', '', $text); // With the exception of the rules above, we consider all punctuation, // marks, spacers, etc, to be a word boundary. $text = preg_replace('/['. PREG_CLASS_SEARCH_EXCLUDE .']+/u', ' ', $text); return $text; } /** * Basic CJK tokenizer. Simply splits a string into consecutive, overlapping * sequences of characters ('minimum_word_size' long). */ function search_expand_cjk($matches) { $min = variable_get('minimum_word_size', 3); $str = $matches[0]; $l = drupal_strlen($str); // Passthrough short words if ($l <= $min) { return ' '. $str .' '; } $tokens = ' '; // FIFO queue of characters $chars = array(); // Begin loop for ($i = 0; $i < $l; ++$i) { // Grab next character $current = drupal_substr($str, 0, 1); $str = substr($str, strlen($current)); $chars[] = $current; if ($i >= $min - 1) { $tokens .= implode('', $chars) .' '; array_shift($chars); } } return $tokens; } /** * Splits a string into tokens for indexing. */ function search_index_split($text) { static $last = NULL; static $lastsplit = NULL; if ($last == $text) { return $lastsplit; } // Process words $text = search_simplify($text); $words = explode(' ', $text); array_walk($words, '_search_index_truncate'); // Save last keyword result $last = $text; $lastsplit = $words; return $words; } /** * Helper function for array_walk in search_index_split. */ function _search_index_truncate(&$text) { $text = truncate_utf8($text, 50); } /** * Invokes hook_search_preprocess() in modules. */ function search_preprocess(&$text) { foreach (module_implements('search_preprocess') as $module) { $text = module_invoke($module, 'search_preprocess', $text); } } /** * Update the full-text search index for a particular item. * * @param $sid * A number identifying this particular item (e.g. node id). * * @param $type * A string defining this type of item (e.g. 'node') * * @param $text * The content of this item. Must be a piece of HTML text. * * @ingroup search */ function search_index($sid, $type, $text) { $minimum_word_size = variable_get('minimum_word_size', 3); // Link matching global $base_url; $node_regexp = '@href=[\'"]?(?:'. preg_quote($base_url, '@') .'/|'. preg_quote(base_path(), '@') .')(?:\?q=)?/?((?![a-z]+:)[^\'">]+)[\'">]@i'; // Multipliers for scores of words inside certain HTML tags. // Note: 'a' must be included for link ranking to work. $tags = array('h1' => 25, 'h2' => 18, 'h3' => 15, 'h4' => 12, 'h5' => 9, 'h6' => 6, 'u' => 3, 'b' => 3, 'i' => 3, 'strong' => 3, 'em' => 3, 'a' => 10); // Strip off all ignored tags to speed up processing, but insert space before/after // them to keep word boundaries. $text = str_replace(array('<', '>'), array(' <', '> '), $text); $text = strip_tags($text, '<'. implode('><', array_keys($tags)) .'>'); // Split HTML tags from plain text. $split = preg_split('/\s*<([^>]+?)>\s*/', $text, -1, PREG_SPLIT_DELIM_CAPTURE); // Note: PHP ensures the array consists of alternating delimiters and literals // and begins and ends with a literal (inserting $null as required). $tag = FALSE; // Odd/even counter. Tag or no tag. $link = FALSE; // State variable for link analyser $score = 1; // Starting score per word $accum = ' '; // Accumulator for cleaned up data $tagstack = array(); // Stack with open tags $tagwords = 0; // Counter for consecutive words $focus = 1; // Focus state $results = array(0 => array()); // Accumulator for words for index foreach ($split as $value) { if ($tag) { // Increase or decrease score per word based on tag list($tagname) = explode(' ', $value, 2); $tagname = drupal_strtolower($tagname); // Closing or opening tag? if ($tagname[0] == '/') { $tagname = substr($tagname, 1); // If we encounter unexpected tags, reset score to avoid incorrect boosting. if (!count($tagstack) || $tagstack[0] != $tagname) { $tagstack = array(); $score = 1; } else { // Remove from tag stack and decrement score $score = max(1, $score - $tags[array_shift($tagstack)]); } if ($tagname == 'a') { $link = FALSE; } } else { if ($tagstack[0] == $tagname) { // None of the tags we look for make sense when nested identically. // If they are, it's probably broken HTML. $tagstack = array(); $score = 1; } else { // Add to open tag stack and increment score array_unshift($tagstack, $tagname); $score += $tags[$tagname]; } if ($tagname == 'a') { // Check if link points to a node on this site if (preg_match($node_regexp, $value, $match)) { $path = drupal_get_normal_path($match[1]); if (preg_match('!(?:node|book)/(?:view/)?([0-9]+)!i', $path, $match)) { $linknid = $match[1]; if ($linknid > 0) { // Note: ignore links to uncachable nodes to avoid redirect bugs. $node = db_fetch_object(db_query('SELECT n.title, n.nid, n.vid, r.format FROM {node} n INNER JOIN {node_revisions} r ON n.vid = r.vid WHERE n.nid = %d', $linknid)); if (filter_format_allowcache($node->format)) { $link = TRUE; $linktitle = $node->title; } } } } } } // A tag change occurred, reset counter. $tagwords = 0; } else { // Note: use of PREG_SPLIT_DELIM_CAPTURE above will introduce empty values if ($value != '') { if ($link) { // Check to see if the node link text is its URL. If so, we use the target node title instead. if (preg_match('!^https?://!i', $value)) { $value = $linktitle; } } $words = search_index_split($value); foreach ($words as $word) { // Add word to accumulator $accum .= $word .' '; $num = is_numeric($word); // Check wordlength if ($num || drupal_strlen($word) >= $minimum_word_size) { // Normalize numbers if ($num) { $word = (int)ltrim($word, '-0'); } if ($link) { if (!isset($results[$linknid])) { $results[$linknid] = array(); } $results[$linknid][$word] += $score * $focus; } else { $results[0][$word] += $score * $focus; // Focus is a decaying value in terms of the amount of unique words up to this point. // From 100 words and more, it decays, to e.g. 0.5 at 500 words and 0.3 at 1000 words. $focus = min(1, .01 + 3.5 / (2 + count($results[0]) * .015)); } } $tagwords++; // Too many words inside a single tag probably mean a tag was accidentally left open. if (count($tagstack) && $tagwords >= 15) { $tagstack = array(); $score = 1; } } } } $tag = !$tag; } search_wipe($sid, $type, TRUE); // Insert cleaned up data into dataset db_query("INSERT INTO {search_dataset} (sid, type, data) VALUES (%d, '%s', '%s')", $sid, $type, $accum); // Insert results into search index foreach ($results[0] as $word => $score) { db_query("INSERT INTO {search_index} (word, sid, type, score) VALUES ('%s', %d, '%s', %f)", $word, $sid, $type, $score); search_dirty($word); } unset($results[0]); // Now insert links to nodes foreach ($results as $nid => $words) { foreach ($words as $word => $score) { db_query("INSERT INTO {search_index} (word, sid, type, fromsid, fromtype, score) VALUES ('%s', %d, '%s', %d, '%s', %f)", $word, $nid, 'node', $sid, $type, $score); search_dirty($word); } } } /** * Extract a module-specific search option from a search query. e.g. 'type:book' */ function search_query_extract($keys, $option) { if (preg_match('/(^| )'. $option .':([^ ]*)( |$)/i', $keys, $matches)) { return $matches[2]; } } /** * Return a query with the given module-specific search option inserted in. * e.g. 'type:book'. */ function search_query_insert($keys, $option, $value = '') { if (search_query_extract($keys, $option)) { $keys = trim(preg_replace('/(^| )'. $option .':[^ ]*/i', '', $keys)); } if ($value != '') { $keys .= ' '. $option .':'. $value; } return $keys; } /** * Parse a search query into SQL conditions. * * We build a query that matches the dataset bodies. */ function search_parse_query($text) { $keys = array('positive' => array(), 'negative' => array()); // Tokenize query string preg_match_all('/ (-?)("[^"]+"|[^" ]+)/i', ' '. $text, $matches, PREG_SET_ORDER); if (count($matches) < 1) { return NULL; } // Classify tokens $or = FALSE; foreach ($matches as $match) { $phrase = FALSE; // Strip off phrase quotes if ($match[2]{0} == '"') { $match[2] = substr($match[2], 1, -1); $phrase = TRUE; } // Simplify keyword according to indexing rules and external preprocessors $words = search_simplify($match[2]); // Re-explode in case simplification added more words, except when matching a phrase $words = $phrase ? array($words) : preg_split('/ /', $words, -1, PREG_SPLIT_NO_EMPTY); // Negative matches if ($match[1] == '-') { $keys['negative'] = array_merge($keys['negative'], $words); } // OR operator: instead of a single keyword, we store an array of all // OR'd keywords. elseif ($match[2] == 'OR' && count($keys['positive'])) { $last = array_pop($keys['positive']); // Starting a new OR? if (!is_array($last)) { $last = array($last); } $keys['positive'][] = $last; $or = TRUE; continue; } // Plain keyword else { if ($or) { // Add to last element (which is an array) $keys['positive'][count($keys['positive']) - 1] = array_merge($keys['positive'][count($keys['positive']) - 1], $words); } else { $keys['positive'] = array_merge($keys['positive'], $words); } } $or = FALSE; } // Convert keywords into SQL statements. $query = array(); $query2 = array(); $arguments = array(); $arguments2 = array(); $matches = 0; // Positive matches foreach ($keys['positive'] as $key) { // Group of ORed terms if (is_array($key) && count($key)) { $queryor = array(); $any = FALSE; foreach ($key as $or) { list($q, $count) = _search_parse_query($or, $arguments2); $any |= $count; if ($q) { $queryor[] = $q; $arguments[] = $or; } } if (count($queryor)) { $query[] = '('. implode(' OR ', $queryor) .')'; // A group of OR keywords only needs to match once $matches += ($any > 0); } } // Single ANDed term else { list($q, $count) = _search_parse_query($key, $arguments2); if ($q) { $query[] = $q; $arguments[] = $key; // Each AND keyword needs to match at least once $matches += $count; } } } // Negative matches foreach ($keys['negative'] as $key) { list($q) = _search_parse_query($key, $arguments2, TRUE); if ($q) { $query[] = $q; $arguments[] = $key; } } $query = implode(' AND ', $query); // Build word-index conditions for the first pass $query2 = substr(str_repeat("i.word = '%s' OR ", count($arguments2)), 0, -4); return array($query, $arguments, $query2, $arguments2, $matches); } /** * Helper function for search_parse_query(); */ function _search_parse_query(&$word, &$scores, $not = FALSE) { $count = 0; // Determine the scorewords of this word/phrase if (!$not) { $split = explode(' ', $word); foreach ($split as $s) { $num = is_numeric($s); if ($num || drupal_strlen($s) >= variable_get('minimum_word_size', 3)) { $s = $num ? ((int)ltrim($s, '-0')) : $s; if (!isset($scores[$s])) { $scores[$s] = $s; $count++; } } } } // Return matching snippet and number of added words return array("d.data ". ($not ? 'NOT ' : '') ."LIKE '%% %s %%'", $count); } /** * Do a query on the full-text search index for a word or words. * * This function is normally only called by each module that support the * indexed search (and thus, implements hook_update_index()). * * Two queries are performed which can be extended by the caller. * * The first query selects a set of possible matches based on the search index * and any extra given restrictions. This is the classic "OR" search. * * SELECT i.type, i.sid, SUM(i.score*t.count) AS relevance * FROM {search_index} i * INNER JOIN {search_total} t ON i.word = t.word * $join1 * WHERE $where1 AND (...) * GROUP BY i.type, i.sid * * The second query further refines this set by verifying advanced text * conditions (such as AND, negative or phrase matches), and orders the results * on a the column or expression 'score': * * SELECT i.type, i.sid, $select2 * FROM temp_search_sids i * INNER JOIN {search_dataset} d ON i.sid = d.sid AND i.type = d.type * $join2 * WHERE (...) * ORDER BY score DESC * * @param $keywords * A search string as entered by the user. * * @param $type * A string identifying the calling module. * * @param $join1 * (optional) Inserted into the JOIN part of the first SQL query. * For example "INNER JOIN {node} n ON n.nid = i.sid". * * @param $where1 * (optional) Inserted into the WHERE part of the first SQL query. * For example "(n.status > %d)". * * @param $arguments1 * (optional) Extra SQL arguments belonging to the first query. * * @param $select2 * (optional) Inserted into the SELECT pat of the second query. Must contain * a column selected as 'score'. * defaults to 'i.relevance AS score' * * @param $join2 * (optional) Inserted into the JOIN par of the second SQL query. * For example "INNER JOIN {node_comment_statistics} n ON n.nid = i.sid" * * @param $arguments2 * (optional) Extra SQL arguments belonging to the second query parameter. * * @param $sort_parameters * (optional) SQL arguments for sorting the final results. * Default: 'ORDER BY score DESC' * * @return * An array of SIDs for the search results. * * @ingroup search */ function do_search($keywords, $type, $join1 = '', $where1 = '1', $arguments1 = array(), $select2 = 'i.relevance AS score', $join2 = '', $arguments2 = array(), $sort_parameters = 'ORDER BY score DESC') { $query = search_parse_query($keywords); if ($query[2] == '') { form_set_error('keys', t('You must include at least one positive keyword with @count characters or more.', array('@count' => variable_get('minimum_word_size', 3)))); } if ($query === NULL || $query[0] == '' || $query[2] == '') { return array(); } // First pass: select all possible matching sids, doing a simple index-based OR matching on the keywords. // 'matches' is used to reject those items that cannot possibly match the query. $conditions = $where1 .' AND ('. $query[2] .") AND i.type = '%s'"; $arguments = array_merge($arguments1, $query[3], array($type, $query[4])); $result = db_query_temporary("SELECT i.type, i.sid, SUM(i.score * t.count) AS relevance, COUNT(*) AS matches FROM {search_index} i INNER JOIN {search_total} t ON i.word = t.word $join1 WHERE $conditions GROUP BY i.type, i.sid HAVING COUNT(*) >= %d", $arguments, 'temp_search_sids'); // Calculate maximum relevance, to normalize it $normalize = db_result(db_query('SELECT MAX(relevance) FROM temp_search_sids')); if (!$normalize) { return array(); } $select2 = str_replace('i.relevance', '('. (1.0 / $normalize) .' * i.relevance)', $select2); // Second pass: only keep items that match the complicated keywords conditions (phrase search, negative keywords, ...) $conditions = '('. $query[0] .')'; $arguments = array_merge($arguments2, $query[1]); $result = db_query_temporary("SELECT i.type, i.sid, $select2 FROM temp_search_sids i INNER JOIN {search_dataset} d ON i.sid = d.sid AND i.type = d.type $join2 WHERE $conditions $sort_parameters", $arguments, 'temp_search_results'); if (($count = db_result(db_query('SELECT COUNT(*) FROM temp_search_results'))) == 0) { return array(); } $count_query = "SELECT $count"; // Do actual search query $result = pager_query("SELECT * FROM temp_search_results", 10, 0, $count_query); $results = array(); while ($item = db_fetch_object($result)) { $results[] = $item; } return $results; } /** * Helper function for grabbing search keys. */ function search_get_keys() { // Extract keys as remainder of path // Note: support old GET format of searches for existing links. $path = explode('/', $_GET['q'], 3); return count($path) == 3 ? $path[2] : $_REQUEST['keys']; } /** * Menu callback; presents the search form and/or search results. */ function search_view() { $type = arg(1); // Search form submits with POST but redirects to GET. This way we can keep // the search query URL clean as a whistle: // search/type/keyword+keyword if (!isset($_POST['form_id'])) { if ($type == '') { // Note: search/node can not be a default tab because it would take on the // path of its parent (search). It would prevent remembering keywords when // switching tabs. This is why we drupal_goto to it from the parent instead. drupal_goto('search/node'); } $keys = search_get_keys(); // Only perform search if there is non-whitespace search term: if (trim($keys)) { // Log the search keys: watchdog('search', t('%keys (@type).', array('%keys' => $keys, '@type' => module_invoke($type, 'search', 'name'))), WATCHDOG_NOTICE, l(t('results'), 'search/'. $type .'/'. $keys)); // Collect the search results: $results = search_data($keys, $type); if ($results) { $results = theme('box', t('Search results'), $results); } else { $results = theme('box', t('Your search yielded no results'), search_help('search#noresults')); } } // Construct the search form. $output = drupal_get_form('search_form', NULL, $keys, $type); $output .= $results; return $output; } return drupal_get_form('search_form', NULL, $keys, $type); } /** * @defgroup search Search interface * @{ * The Drupal search interface manages a global search mechanism. * * Modules may plug into this system to provide searches of different types of * data. Most of the system is handled by search.module, so this must be enabled * for all of the search features to work. * * There are three ways to interact with the search system: * - Specifically for searching nodes, you can implement nodeapi('update index') * and nodeapi('search result'). However, note that the search system already * indexes all visible output of a node, i.e. everything displayed normally * by hook_view() and hook_nodeapi('view'). This is usually sufficient. * You should only use this mechanism if you want additional, non-visible data * to be indexed. * - Implement hook_search(). This will create a search tab for your module on * the /search page with a simple keyword search form. You may optionally * implement hook_search_item() to customize the display of your results. * - Implement hook_update_index(). This allows your module to use Drupal's * HTML indexing mechanism for searching full text efficiently. * * If your module needs to provide a more complicated search form, then you need * to implement it yourself without hook_search(). In that case, you should * define it as a local task (tab) under the /search page (e.g. /search/mymodule) * so that users can easily find it. */ /** * Render a search form. * * @param $action * Form action. Defaults to "search". * @param $keys * The search string entered by the user, containing keywords for the search. * @param $type * The type of search to render the node for. Must be the name of module * which implements hook_search(). Defaults to 'node'. * @param $prompt * A piece of text to put before the form (e.g. "Enter your keywords") * @return * An HTML string containing the search form. */ function search_form($action = '', $keys = '', $type = NULL, $prompt = NULL) { // Add CSS drupal_add_css(drupal_get_path('module', 'search') .'/search.css', 'module', 'all', FALSE); if (!$action) { $action = url('search/'. $type); } if (is_null($prompt)) { $prompt = t('Enter your keywords'); } $form = array( '#action' => $action, '#attributes' => array('class' => 'search-form'), ); $form['module'] = array('#type' => 'value', '#value' => $type); $form['basic'] = array('#type' => 'item', '#title' => $prompt); $form['basic']['inline'] = array('#prefix' => '
    ', '#suffix' => '
    '); $form['basic']['inline']['keys'] = array( '#type' => 'textfield', '#title' => '', '#default_value' => $keys, '#size' => $prompt ? 40 : 20, '#maxlength' => 255, ); // processed_keys is used to coordinate keyword passing between other forms // that hook into the basic search form. $form['basic']['inline']['processed_keys'] = array('#type' => 'value', '#value' => array()); $form['basic']['inline']['submit'] = array('#type' => 'submit', '#value' => t('Search')); return $form; } /** * As the search form collates keys from other modules hooked in via * hook_form_alter, the validation takes place in _submit. * search_form_validate() is used solely to set the 'processed_keys' form * value for the basic search form. */ function search_form_validate($form_id, $form_values, $form) { form_set_value($form['basic']['inline']['processed_keys'], trim($form_values['keys'])); } /** * Process a search form submission. */ function search_form_submit($form_id, $form_values) { $keys = $form_values['processed_keys']; if ($keys == '') { form_set_error('keys', t('Please enter some keywords.')); // Fall through to the drupal_goto() call. } $type = $form_values['module'] ? $form_values['module'] : 'node'; return 'search/'. $type .'/'. $keys; } /** * Output a search form for the search block and the theme's search box. */ function search_box($form_id) { // Use search_keys instead of keys to avoid ID conflicts with the search block. $form[$form_id .'_keys'] = array( '#type' => 'textfield', '#size' => 15, '#default_value' => '', '#attributes' => array('title' => t('Enter the terms you wish to search for.')), ); $form['submit'] = array('#type' => 'submit', '#value' => t('Search')); // Always go to the search page since the search form is not guaranteed to be // on every page. $form['#action'] = url('search/node'); $form['#base'] = 'search_box_form'; return $form; } /** * Process a block search form submission. */ function search_box_form_submit($form_id, $form_values) { return 'search/node/'. trim($form_values[$form_id .'_keys']); } /** * Theme the theme search form. */ function theme_search_theme_form($form) { return ''; } /** * Theme the block search form. */ function theme_search_block_form($form) { return '
    '. drupal_render($form) .'
    '; } /** * Perform a standard search on the given keys, and return the formatted results. */ function search_data($keys = NULL, $type = 'node') { if (isset($keys)) { if (module_hook($type, 'search')) { $results = module_invoke($type, 'search', 'search', $keys); if (isset($results) && is_array($results) && count($results)) { if (module_hook($type, 'search_page')) { return module_invoke($type, 'search_page', $results); } else { return theme('search_page', $results, $type); } } } } } /** * Returns snippets from a piece of text, with certain keywords highlighted. * Used for formatting search results. * * @param $keys * A string containing a search query. * * @param $text * The text to extract fragments from. * * @return * A string containing HTML for the excerpt. */ function search_excerpt($keys, $text) { // We highlight around non-indexable or CJK characters. $boundary = '(?:(?<=['. PREG_CLASS_SEARCH_EXCLUDE . PREG_CLASS_CJK .'])|(?=['. PREG_CLASS_SEARCH_EXCLUDE . PREG_CLASS_CJK .']))'; // Extract positive keywords and phrases preg_match_all('/ ("([^"]+)"|(?!OR)([^" ]+))/', ' '. $keys, $matches); $keys = array_merge($matches[2], $matches[3]); // Prepare text $text = ' '. strip_tags(str_replace(array('<', '>'), array(' <', '> '), $text)) .' '; array_walk($keys, '_search_excerpt_replace'); $workkeys = $keys; // Extract a fragment per keyword for at most 4 keywords. // First we collect ranges of text around each keyword, starting/ending // at spaces. // If the sum of all fragments is too short, we look for second occurrences. $ranges = array(); $included = array(); $length = 0; while ($length < 256 && count($workkeys)) { foreach ($workkeys as $k => $key) { if (strlen($key) == 0) { unset($workkeys[$k]); unset($keys[$k]); continue; } if ($length >= 256) { break; } // Remember occurrence of key so we can skip over it if more occurrences // are desired. if (!isset($included[$key])) { $included[$key] = 0; } // Locate a keyword (position $p), then locate a space in front (position // $q) and behind it (position $s) if (preg_match('/'. $boundary . $key . $boundary .'/iu', $text, $match, PREG_OFFSET_CAPTURE, $included[$key])) { $p = $match[0][1]; if (($q = strpos($text, ' ', max(0, $p - 60))) !== FALSE) { $end = substr($text, $p, 80); if (($s = strrpos($end, ' ')) !== FALSE) { $ranges[$q] = $p + $s; $length += $p + $s - $q; $included[$key] = $p + 1; } else { unset($workkeys[$k]); } } else { unset($workkeys[$k]); } } else { unset($workkeys[$k]); } } } // If we didn't find anything, return the beginning. if (count($ranges) == 0) { return truncate_utf8($text, 256) .' ...'; } // Sort the text ranges by starting position. ksort($ranges); // Now we collapse overlapping text ranges into one. The sorting makes it O(n). $newranges = array(); foreach ($ranges as $from2 => $to2) { if (!isset($from1)) { $from1 = $from2; $to1 = $to2; continue; } if ($from2 <= $to1) { $to1 = max($to1, $to2); } else { $newranges[$from1] = $to1; $from1 = $from2; $to1 = $to2; } } $newranges[$from1] = $to1; // Fetch text $out = array(); foreach ($newranges as $from => $to) { $out[] = substr($text, $from, $to - $from); } $text = (isset($newranges[0]) ? '' : '... '). implode(' ... ', $out) .' ...'; // Highlight keywords. Must be done at once to prevent conflicts ('strong' and ''). $text = preg_replace('/'. $boundary .'('. implode('|', $keys) .')'. $boundary .'/iu', '\0', $text); return $text; } /** * @} End of "defgroup search". */ /** * Helper function for array_walk in search_except. */ function _search_excerpt_replace(&$text) { $text = preg_quote($text, '/'); } /** * Format a single result entry of a search query. This function is normally * called by theme_search_page() or hook_search_page(). * * @param $item * A single search result as returned by hook_search(). The result should be * an array with keys "link", "title", "type", "user", "date", and "snippet". * Optionally, "extra" can be an array of extra info to show along with the * result. * @param $type * The type of item found, such as "user" or "node". * * @ingroup themeable */ function theme_search_item($item, $type) { $output = '
    '. check_plain($item['title']) .'
    '; $info = array(); if ($item['type']) { $info[] = check_plain($item['type']); } if ($item['user']) { $info[] = $item['user']; } if ($item['date']) { $info[] = format_date($item['date'], 'small'); } if (is_array($item['extra'])) { $info = array_merge($info, $item['extra']); } $output .= '
    '. ($item['snippet'] ? '

    '. $item['snippet'] .'

    ' : '') .'

    '. implode(' - ', $info) .'

    '; return $output; } /** * Format the result page of a search query. * * Modules may implement hook_search_page() in order to override this default * function to display search results. In that case it is expected they provide * their own themeable functions. * * @param $results * All search result as returned by hook_search(). * @param $type * The type of item found, such as "user" or "node". * * @ingroup themeable */ function theme_search_page($results, $type) { $output = '
    '; foreach ($results as $entry) { $output .= theme('search_item', $entry, $type); } $output .= '
    '; $output .= theme('pager', NULL, 10, 0); return $output; } function search_forms() { $forms['search_theme_form']= array( 'callback' => 'search_box', 'callback arguments' => array('search_theme_form'), ); $forms['search_block_form']= array( 'callback' => 'search_box', 'callback arguments' => array('search_block_form'), ); return $forms; } loki_website/modules/search/search.install0000644000004100000410000000472310677574064021366 0ustar www-datawww-data'. t('The statistics module keeps track of numerous statistics of site usage. It counts how many times, and from where each of your posts is viewed. The statistics module can be used to learn many useful things about how users are interacting with each other and with your site.') .'

    '; $output .= t('

    Statistics module features

    • Logs show statistics for how many times your site and specific content on your site has been accessed.
    • Referrers tells you from where visitors came from (referrer URL).
    • Top pages shows you what\'s hot, what is the most popular content on your site.
    • Top users shows you the most active users for your site.
    • Recent hits displays information about the latest activity on your site.
    • Node count displays the number of times a node has been accessed in the node\'s link section next to # comments.
    • Popular content block creates a block that can display the day\'s top viewed content, the all time top viewed content, and the last content viewed.
    '); $output .= t('

    Configuring the statistics module

    • Enable access log allows you to turn the access log on and off. This log is used to store data about every page accessed, such as the remote host\'s IP address, where they came from (referrer), what node they\'ve viewed, and their user name. Enabling the log adds one database call per page displayed by Drupal.
    • Discard access logs older than allows you to configure how long an access log entry is saved, after which time it is deleted from the database table. To use this you need to run cron.php
    • Enable node view counter allows you to turn on and off the node-counting functionality of this module. If it is turned on, an extra database query is added for each node displayed, which increments a counter.
    '); $output .= '

    '. t('For more information please read the configuration and customization handbook Statistics page.', array('@statistics' => 'http://drupal.org/handbook/modules/statistics/')) .'

    '; return $output; case 'admin/logs/settings': return '

    '. t('Settings for the statistical information that Drupal will keep about the site. See site statistics for the actual information.', array('@statistics' => url('admin/logs/hits'))) .'

    '; case 'admin/logs/hits': return '

    '. t('This page shows you the most recent hits.') .'

    '; case 'admin/logs/referrers': return '

    '. t('This page shows you all external referrers. These are links pointing to your web site from outside your web site.') .'

    '; case 'admin/logs/visitors': return '

    '. t("When you ban a visitor, you prevent the visitor's IP address from accessing your site. Unlike blocking a user, banning a visitor works even for anonymous users. The most common use for this is to block bots/web crawlers that are consuming too many resources.") .'

    '; } } /** * Implementation of hook_exit(). * * This is where statistics are gathered on page accesses. */ function statistics_exit() { global $user, $recent_activity; drupal_bootstrap(DRUPAL_BOOTSTRAP_PATH); if (variable_get('statistics_count_content_views', 0)) { // We are counting content views. if ((arg(0) == 'node') && is_numeric(arg(1)) && arg(2) == '') { // A node has been viewed, so update the node's counters. db_query('UPDATE {node_counter} SET daycount = daycount + 1, totalcount = totalcount + 1, timestamp = %d WHERE nid = %d', time(), arg(1)); // If we affected 0 rows, this is the first time viewing the node. if (!db_affected_rows()) { // We must create a new row to store counters for the new node. db_query('INSERT INTO {node_counter} (nid, daycount, totalcount, timestamp) VALUES (%d, 1, 1, %d)', arg(1), time()); } } } if ((variable_get('statistics_enable_access_log', 0)) && (module_invoke('throttle', 'status') == 0)) { // Log this page access. db_query("INSERT INTO {accesslog} (title, path, url, hostname, uid, sid, timer, timestamp) values('%s', '%s', '%s', '%s', %d, '%s', %d, %d)", strip_tags(drupal_get_title()), $_GET['q'], referer_uri(), $_SERVER['REMOTE_ADDR'], $user->uid, session_id(), timer_read('page'), time()); } } /** * Implementation of hook_perm(). */ function statistics_perm() { return array('access statistics', 'view post access counter'); } /** * Implementation of hook_link(). */ function statistics_link($type, $node = NULL, $teaser = FALSE) { global $id; $links = array(); if ($type != 'comment' && user_access('view post access counter')) { $statistics = statistics_get($node->nid); if ($statistics) { $links['statistics_counter']['title'] = format_plural($statistics['totalcount'], '1 read', '@count reads'); } } return $links; } /** * Implementation of hook_menu(). */ function statistics_menu($may_cache) { $items = array(); $access = user_access('access statistics'); if ($may_cache) { $items[] = array( 'path' => 'admin/logs/hits', 'title' => t('Recent hits'), 'description' => t('View pages that have recently been visited.'), 'callback' => 'statistics_recent_hits', 'access' => $access); $items[] = array( 'path' => 'admin/logs/pages', 'title' => t('Top pages'), 'description' => t('View pages that have been hit frequently.'), 'callback' => 'statistics_top_pages', 'access' => $access, 'weight' => 1); $items[] = array( 'path' => 'admin/logs/visitors', 'title' => t('Top visitors'), 'description' => t('View visitors that hit many pages.'), 'callback' => 'statistics_top_visitors', 'access' => $access, 'weight' => 2); $items[] = array( 'path' => 'admin/logs/referrers', 'title' => t('Top referrers'), 'description' => t('View top referrers.'), 'callback' => 'statistics_top_referrers', 'access' => $access); $items[] = array( 'path' => 'admin/logs/access', 'title' => t('Details'), 'description' => t('View access log.'), 'callback' => 'statistics_access_log', 'access' => $access, 'type' => MENU_CALLBACK); $items[] = array( 'path' => 'admin/logs/settings', 'title' => t('Access log settings'), 'description' => t('Control details about what and how your site logs.'), 'callback' => 'drupal_get_form', 'callback arguments' => array('statistics_access_logging_settings'), 'access' => user_access('administer site configuration'), 'type' => MENU_NORMAL_ITEM, 'weight' => 3); } else { if (arg(0) == 'user' && is_numeric(arg(1)) && variable_get('statistics_enable_access_log', 0)) { $items[] = array( 'path' => 'user/'. arg(1) .'/track/navigation', 'title' => t('Track page visits'), 'callback' => 'statistics_user_tracker', 'access' => $access, 'type' => MENU_LOCAL_TASK, 'weight' => 2); } if (arg(0) == 'node' && is_numeric(arg(1)) && variable_get('statistics_enable_access_log', 0)) { $items[] = array( 'path' => 'node/'. arg(1) .'/track', 'title' => t('Track'), 'callback' => 'statistics_node_tracker', 'access' => $access, 'type' => MENU_LOCAL_TASK, 'weight' => 2); } } return $items; } /** * Implementation of hook_user(). */ function statistics_user($op, &$edit, &$user) { if ($op == 'delete') { db_query('UPDATE {accesslog} SET uid = 0 WHERE uid = %d', $user->uid); } } function statistics_access_log($aid) { $result = db_query('SELECT a.*, u.name FROM {accesslog} a LEFT JOIN {users} u ON a.uid = u.uid WHERE aid = %d', $aid); if ($access = db_fetch_object($result)) { $output = ''; $output .= ' "; $output .= ' '; // safe because it comes from drupal_get_title() $output .= ' "; $output .= ' '; $output .= ' '; $output .= ' '; $output .= '
    '. t('URL') ."". l(url($access->path, NULL, NULL, TRUE), $access->path) ."
    '. t('Title') .''. $access->title .'
    '. t('Referrer') ."". ($access->url ? l($access->url, $access->url) : '') ."
    '. t('Date') .''. format_date($access->timestamp, 'large') .'
    '. t('User') .''. theme('username', $access) .'
    '. t('Hostname') .''. check_plain($access->hostname) .'
    '; return $output; } else { drupal_not_found(); } } function statistics_node_tracker() { if ($node = node_load(arg(1))) { $header = array( array('data' => t('Time'), 'field' => 'a.timestamp', 'sort' => 'desc'), array('data' => t('Referrer'), 'field' => 'a.url'), array('data' => t('User'), 'field' => 'u.name'), array('data' => t('Operations'))); $result = pager_query('SELECT a.aid, a.timestamp, a.url, a.uid, u.name FROM {accesslog} a LEFT JOIN {users} u ON a.uid = u.uid WHERE a.path LIKE \'node/%d%%\'' . tablesort_sql($header), 30, 0, NULL, $node->nid); while ($log = db_fetch_object($result)) { $rows[] = array( array('data' => format_date($log->timestamp, 'small'), 'class' => 'nowrap'), _statistics_link($log->url), theme('username', $log), l(t('details'), "admin/logs/access/$log->aid")); } drupal_set_title(check_plain($node->title)); $output = theme('table', $header, $rows); $output .= theme('pager', NULL, 30, 0); return $output; } else { drupal_not_found(); } } function statistics_user_tracker() { if ($account = user_load(array('uid' => arg(1)))) { $header = array( array('data' => t('Timestamp'), 'field' => 'timestamp', 'sort' => 'desc'), array('data' => t('Page'), 'field' => 'path'), array('data' => t('Operations'))); $result = pager_query('SELECT aid, timestamp, path, title FROM {accesslog} WHERE uid = %d' . tablesort_sql($header), 30, 0, NULL, $account->uid); while ($log = db_fetch_object($result)) { $rows[] = array( array('data' => format_date($log->timestamp, 'small'), 'class' => 'nowrap'), _statistics_format_item($log->title, $log->path), l(t('details'), "admin/logs/access/$log->aid")); } drupal_set_title(check_plain($account->name)); $output = theme('table', $header, $rows); $output .= theme('pager', NULL, 30, 0); return $output; } else { drupal_not_found(); } } /** * Menu callback; presents the "recent hits" page. */ function statistics_recent_hits() { $header = array( array('data' => t('Timestamp'), 'field' => 'a.timestamp', 'sort' => 'desc'), array('data' => t('Page'), 'field' => 'a.path'), array('data' => t('User'), 'field' => 'u.name'), array('data' => t('Operations')) ); $sql = 'SELECT a.aid, a.path, a.title, a.uid, u.name, a.timestamp FROM {accesslog} a LEFT JOIN {users} u ON u.uid = a.uid' . tablesort_sql($header); $result = pager_query($sql, 30); while ($log = db_fetch_object($result)) { $rows[] = array( array('data' => format_date($log->timestamp, 'small'), 'class' => 'nowrap'), _statistics_format_item($log->title, $log->path), theme('username', $log), l(t('details'), "admin/logs/access/$log->aid")); } $output = theme('table', $header, $rows); $output .= theme('pager', NULL, 30, 0); return $output; } /** * Menu callback; presents the "top pages" page. */ function statistics_top_pages() { $sql = "SELECT COUNT(path) AS hits, path, title, AVG(timer) AS average_time, SUM(timer) AS total_time FROM {accesslog} GROUP BY path, title"; $sql_cnt = "SELECT COUNT(DISTINCT(path)) FROM {accesslog}"; $header = array( array('data' => t('Hits'), 'field' => 'hits', 'sort' => 'desc'), array('data' => t('Page'), 'field' => 'path'), array('data' => t('Average page generation time'), 'field' => 'average_time'), array('data' => t('Total page generation time'), 'field' => 'total_time') ); $sql .= tablesort_sql($header); $result = pager_query($sql, 30, 0, $sql_cnt); while ($page = db_fetch_object($result)) { $rows[] = array($page->hits, _statistics_format_item($page->title, $page->path), t('%time ms', array('%time' => round($page->average_time))), format_interval(round($page->total_time / 1000))); } drupal_set_title(t('Top pages in the past %interval', array('%interval' => format_interval(variable_get('statistics_flush_accesslog_timer', 259200))))); $output = theme('table', $header, $rows); $output .= theme('pager', NULL, 30, 0); return $output; } /** * Menu callback; presents the "top visitors" page. */ function statistics_top_visitors() { $header = array( array('data' => t('Hits'), 'field' => 'hits', 'sort' => 'desc'), array('data' => t('Visitor'), 'field' => 'u.name'), array('data' => t('Total page generation time'), 'field' => 'total'), array('data' => t('Operations')) ); $sql = "SELECT COUNT(a.uid) AS hits, a.uid, u.name, a.hostname, SUM(a.timer) AS total, ac.aid FROM {accesslog} a LEFT JOIN {access} ac ON ac.type = 'host' AND LOWER(a.hostname) LIKE (ac.mask) LEFT JOIN {users} u ON a.uid = u.uid GROUP BY a.hostname, a.uid, u.name, ac.aid". tablesort_sql($header); $sql_cnt = "SELECT COUNT(DISTINCT(CONCAT(uid, hostname))) FROM {accesslog}"; $result = pager_query($sql, 30, 0, $sql_cnt); while ($account = db_fetch_object($result)) { $qs = drupal_get_destination(); $ban_link = $account->aid ? l(t('unban'), "admin/user/rules/delete/$account->aid", array(), $qs) : l(t('ban'), "admin/user/rules/add/$account->hostname/host", array(), $qs); $rows[] = array($account->hits, ($account->uid ? theme('username', $account) : $account->hostname), format_interval(round($account->total / 1000)), $ban_link); } drupal_set_title(t('Top visitors in the past %interval', array('%interval' => format_interval(variable_get('statistics_flush_accesslog_timer', 259200))))); $output = theme('table', $header, $rows); $output .= theme('pager', NULL, 30, 0); return $output; } /** * Menu callback; presents the "referrer" page. */ function statistics_top_referrers() { $query = "SELECT url, COUNT(url) AS hits, MAX(timestamp) AS last FROM {accesslog} WHERE url NOT LIKE '%%%s%%' AND url <> '' GROUP BY url"; $query_cnt = "SELECT COUNT(DISTINCT(url)) FROM {accesslog} WHERE url <> '' AND url NOT LIKE '%%%s%%'"; drupal_set_title(t('Top referrers in the past %interval', array('%interval' => format_interval(variable_get('statistics_flush_accesslog_timer', 259200))))); $header = array( array('data' => t('Hits'), 'field' => 'hits', 'sort' => 'desc'), array('data' => t('Url'), 'field' => 'url'), array('data' => t('Last visit'), 'field' => 'last'), ); $query .= tablesort_sql($header); $result = pager_query($query, 30, 0, $query_cnt, $_SERVER['HTTP_HOST']); while ($referrer = db_fetch_object($result)) { $rows[] = array($referrer->hits, _statistics_link($referrer->url), t('@time ago', array('@time' => format_interval(time() - $referrer->last)))); } $output = theme('table', $header, $rows); $output .= theme('pager', NULL, 30, 0); return $output; } function statistics_access_logging_settings() { // Access log settings: $options = array('1' => t('Enabled'), '0' => t('Disabled')); $form['access'] = array( '#type' => 'fieldset', '#title' => t('Access log settings')); $form['access']['statistics_enable_access_log'] = array( '#type' => 'radios', '#title' => t('Enable access log'), '#default_value' => variable_get('statistics_enable_access_log', 0), '#options' => $options, '#description' => t('Log each page access. Required for referrer statistics.')); $period = drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval'); $form['access']['statistics_flush_accesslog_timer'] = array( '#type' => 'select', '#title' => t('Discard access logs older than'), '#default_value' => variable_get('statistics_flush_accesslog_timer', 259200), '#options' => $period, '#description' => t('Older access log entries (including referrer statistics) will be automatically discarded. Requires crontab.')); // count content views settings $form['content'] = array( '#type' => 'fieldset', '#title' => t('Content viewing counter settings')); $form['content']['statistics_count_content_views'] = array( '#type' => 'radios', '#title' => t('Count content views'), '#default_value' => variable_get('statistics_count_content_views', 0), '#options' => $options, '#description' => t('Increment a counter each time content is viewed.')); return system_settings_form($form); } /** * Implementation of hook_cron(). */ function statistics_cron() { $statistics_timestamp = variable_get('statistics_day_timestamp', ''); if ((time() - $statistics_timestamp) >= 86400) { /* reset day counts */ db_query('UPDATE {node_counter} SET daycount = 0'); variable_set('statistics_day_timestamp', time()); } /* clean expired access logs */ db_query('DELETE FROM {accesslog} WHERE timestamp < %d', time() - variable_get('statistics_flush_accesslog_timer', 259200)); } /** * Returns all time or today top or last viewed node(s). * * @param $dbfield * one of * - 'totalcount': top viewed content of all time. * - 'daycount': top viewed content for today. * - 'timestamp': last viewed node. * * @param $dbrows * number of rows to be returned. * * @return * A query result containing n.nid, n.title, u.uid, u.name of the selected node(s) * or FALSE if the query could not be executed correctly. */ function statistics_title_list($dbfield, $dbrows) { return db_query_range(db_rewrite_sql("SELECT n.nid, n.title, u.uid, u.name FROM {node} n INNER JOIN {node_counter} s ON n.nid = s.nid INNER JOIN {users} u ON n.uid = u.uid WHERE %s <> '0' AND n.status = 1 ORDER BY %s DESC"), 's.'. $dbfield, 's.'. $dbfield, 0, $dbrows); } /** * Retrieves a node's "view statistics". * * @param $nid * node ID * * @return * An array with three entries: [0]=totalcount, [1]=daycount, [2]=timestamp * - totalcount: count of the total number of times that node has been viewed. * - daycount: count of the total number of times that node has been viewed "today". * For the daycount to be reset, cron must be enabled. * - timestamp: timestamp of when that node was last viewed. */ function statistics_get($nid) { if ($nid > 0) { /* retrieves an array with both totalcount and daycount */ $statistics = db_fetch_array(db_query('SELECT totalcount, daycount, timestamp FROM {node_counter} WHERE nid = %d', $nid)); } return $statistics; } /** * Implementation of hook_block(). */ function statistics_block($op = 'list', $delta = 0, $edit = array()) { switch ($op) { case 'list': if (variable_get('statistics_count_content_views', 0)) { $blocks[0]['info'] = t('Popular content'); } return $blocks; case 'configure': // Popular content block settings $numbers = array('0' => t('Disabled')) + drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 40)); $form['statistics_block_top_day_num'] = array('#type' => 'select', '#title' => t("Number of day's top views to display"), '#default_value' => variable_get('statistics_block_top_day_num', 0), '#options' => $numbers, '#description' => t('How many content items to display in "day" list.')); $form['statistics_block_top_all_num'] = array('#type' => 'select', '#title' => t('Number of all time views to display'), '#default_value' => variable_get('statistics_block_top_all_num', 0), '#options' => $numbers, '#description' => t('How many content items to display in "all time" list.')); $form['statistics_block_top_last_num'] = array('#type' => 'select', '#title' => t('Number of most recent views to display'), '#default_value' => variable_get('statistics_block_top_last_num', 0), '#options' => $numbers, '#description' => t('How many content items to display in "recently viewed" list.')); return $form; case 'save': variable_set('statistics_block_top_day_num', $edit['statistics_block_top_day_num']); variable_set('statistics_block_top_all_num', $edit['statistics_block_top_all_num']); variable_set('statistics_block_top_last_num', $edit['statistics_block_top_last_num']); break; case 'view': if (user_access('access content')) { $content = array(); $daytop = variable_get('statistics_block_top_day_num', 0); if ($daytop && ($result = statistics_title_list('daycount', $daytop)) && db_num_rows($result)) { $content[] = node_title_list($result, t("Today's:")); } $alltimetop = variable_get('statistics_block_top_all_num', 0); if ($alltimetop && ($result = statistics_title_list('totalcount', $alltimetop)) && db_num_rows($result)) { $content[] = node_title_list($result, t('All time:')); } $lasttop = variable_get('statistics_block_top_last_num', 0); if ($lasttop && ($result = statistics_title_list('timestamp', $lasttop)) && db_num_rows($result)) { $content[] = node_title_list($result, t('Last viewed:')); } if (count($content)) { $block['content'] = implode('
    ', $content); $block['subject'] = t('Popular content'); return $block; } } } } /** * It is possible to adjust the width of columns generated by the * statistics module. */ function _statistics_link($path, $width = 35) { $title = drupal_get_path_alias($path); $title = truncate_utf8($title, $width, FALSE, TRUE); return l($title, $path); } function _statistics_format_item($title, $path) { $path = ($path ? $path : '/'); $output = ($title ? "$title
    " : ''); $output .= _statistics_link($path); return $output; } /** * Implementation of hook_nodeapi(). */ function statistics_nodeapi(&$node, $op, $arg = 0) { switch ($op) { case 'delete': // clean up statistics table when node is deleted db_query('DELETE FROM {node_counter} WHERE nid = %d', $node->nid); } } loki_website/modules/statistics/statistics.install0000644000004100000410000000443410524204074023215 0ustar www-datawww-data TRUE, 'default' => "''")); break; } return $ret; } /** * Implementation of hook_uninstall(). */ function statistics_uninstall() { db_query('DROP TABLE {accesslog}'); variable_del('statistics_count_content_views'); variable_del('statistics_enable_access_log'); variable_del('statistics_flush_accesslog_timer'); variable_del('statistics_day_timestamp'); variable_del('statistics_block_top_day_num'); variable_del('statistics_block_top_all_num'); variable_del('statistics_block_top_last_num'); } loki_website/modules/comment/0000755000004100000410000000000010744012100016665 5ustar www-datawww-dataloki_website/modules/comment/comment.info0000644000004100000410000000047410741515024021223 0ustar www-datawww-data; $Id: comment.info,v 1.3 2006/11/21 20:55:34 dries Exp $ name = Comment description = Allows users to comment on and discuss published content. package = Core - optional version = VERSION ; Information added by drupal.org packaging script on 2008-01-10 version = "5.6" project = "drupal" datestamp = "1200003604" loki_website/modules/comment/comment.css0000644000004100000410000000031610651443502021054 0ustar www-datawww-data/* $Id: comment.css,v 1.1.2.2 2007/07/24 18:38:58 drumm Exp $ */ .indented { margin-left: 25px; } .comment-unpublished { background-color: #fff4f4; } .preview .comment { background-color: #ffffea; } loki_website/modules/comment/comment.module0000644000004100000410000022220710714270522021556 0ustar www-datawww-data'. t('The comment module creates a discussion board for each post. Users can post comments to discuss a forum topic, weblog post, story, collaborative book page, etc. The ability to comment is an important part of involving members in a community dialogue.') .'

    '; $output .= '

    '. t('An administrator can give comment permissions to user groups, and users can (optionally) edit their last comment, assuming no others have been posted since. Attached to each comment board is a control panel for customizing the way that comments are displayed. Users can control the chronological ordering of posts (newest or oldest first) and the number of posts to display on each page. Comments behave like other user submissions. Filters, smileys and HTML that work in nodes will also work with comments. The comment module provides specific features to inform site members when new comments have been posted.') .'

    '; $output .= '

    '. t('For more information please read the configuration and customization handbook Comment page.', array('@comment' => 'http://drupal.org/handbook/modules/comment/')) .'

    '; return $output; case 'admin/content/comment': case 'admin/content/comment/new': return '

    '. t("Below is a list of the latest comments posted to your site. Click on a subject to see the comment, the author's name to edit the author's user information , 'edit' to modify the text, and 'delete' to remove their submission.") .'

    '; case 'admin/content/comment/approval': return '

    '. t("Below is a list of the comments posted to your site that need approval. To approve a comment, click on 'edit' and then change its 'moderation status' to Approved. Click on a subject to see the comment, the author's name to edit the author's user information, 'edit' to modify the text, and 'delete' to remove their submission.") .'

    '; case 'admin/content/comment/settings': return '

    '. t("Comments can be attached to any node, and their settings are below. The display comes in two types: a 'flat list' where everything is flush to the left side, and comments come in chronological order, and a 'threaded list' where replies to other comments are placed immediately below and slightly indented, forming an outline. They also come in two styles: 'expanded', where you see both the title and the contents, and 'collapsed' where you only see the title. Preview comment forces a user to look at their comment by clicking on a 'Preview' button before they can actually add the comment.") .'

    '; } } /** * Implementation of hook_menu(). */ function comment_menu($may_cache) { $items = array(); if ($may_cache) { $access = user_access('administer comments'); $items[] = array( 'path' => 'admin/content/comment', 'title' => t('Comments'), 'description' => t('List and edit site comments and the comment moderation queue.'), 'callback' => 'comment_admin', 'access' => $access ); // Tabs: $items[] = array('path' => 'admin/content/comment/list', 'title' => t('List'), 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10); // Subtabs: $items[] = array('path' => 'admin/content/comment/list/new', 'title' => t('Published comments'), 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10); $items[] = array('path' => 'admin/content/comment/list/approval', 'title' => t('Approval queue'), 'callback' => 'comment_admin', 'callback arguments' => array('approval'), 'access' => $access, 'type' => MENU_LOCAL_TASK); $items[] = array( 'path' => 'admin/content/comment/settings', 'title' => t('Settings'), 'callback' => 'drupal_get_form', 'callback arguments' => array('comment_admin_settings'), 'access' => $access, 'weight' => 10, 'type' => MENU_LOCAL_TASK); $items[] = array('path' => 'comment/delete', 'title' => t('Delete comment'), 'callback' => 'comment_delete', 'access' => $access, 'type' => MENU_CALLBACK); $access = user_access('post comments'); $items[] = array('path' => 'comment/edit', 'title' => t('Edit comment'), 'callback' => 'comment_edit', 'access' => $access, 'type' => MENU_CALLBACK); } else { if (arg(0) == 'comment' && arg(1) == 'reply' && is_numeric(arg(2))) { $node = node_load(arg(2)); if ($node->nid) { $items[] = array('path' => 'comment/reply', 'title' => t('Reply to comment'), 'callback' => 'comment_reply', 'access' => node_access('view', $node), 'type' => MENU_CALLBACK); } } if ((arg(0) == 'node') && is_numeric(arg(1)) && is_numeric(arg(2))) { $items[] = array( 'path' => ('node/'. arg(1) .'/'. arg(2)), 'title' => t('View'), 'callback' => 'node_page_view', 'callback arguments' => array(node_load(arg(1)), arg(2)), 'type' => MENU_CALLBACK, ); } } return $items; } /** * Implementation of hook_perm(). */ function comment_perm() { return array('access comments', 'post comments', 'administer comments', 'post comments without approval'); } /** * Implementation of hook_block(). * * Generates a block with the most recent comments. */ function comment_block($op = 'list', $delta = 0) { if ($op == 'list') { $blocks[0]['info'] = t('Recent comments'); return $blocks; } else if ($op == 'view' && user_access('access comments')) { $block['subject'] = t('Recent comments'); $block['content'] = theme('comment_block'); return $block; } } /** * Find a number of recent comments. This is done in two steps. * 1. Find the n (specified by $number) nodes that have the most recent * comments. This is done by querying node_comment_statistics which has * an index on last_comment_timestamp, and is thus a fast query. * 2. Loading the information from the comments table based on the nids found * in step 1. * * @param $number (optional) The maximum number of comments to find. * @return $comments An array of comment objects each containing a nid, * subject, cid, and timstamp, or an empty array if there are no recent * comments visible to the current user. */ function comment_get_recent($number = 10) { // Select the $number nodes (visible to the current user) with the most // recent comments. This is efficient due to the index on // last_comment_timestamp. $result = db_query_range(db_rewrite_sql("SELECT nc.nid FROM {node_comment_statistics} nc WHERE nc.comment_count > 0 ORDER BY nc.last_comment_timestamp DESC", 'nc'), 0, $number); $nids = array(); while ($row = db_fetch_object($result)) { $nids[] = $row->nid; } $comments = array(); if (!empty($nids)) { // From among the comments on the nodes selected in the first query, // find the $number most recent comments. $result = db_query_range('SELECT c.nid, c.subject, c.cid, c.timestamp FROM {comments} c INNER JOIN {node} n ON n.nid = c.nid WHERE c.nid IN ('. implode(',', $nids) .') AND n.status = 1 AND c.status = %d ORDER BY c.cid DESC', COMMENT_PUBLISHED, 0, $number); while ($comment = db_fetch_object($result)) { $comments[] = $comment; } } return $comments; } /** * Returns a formatted list of recent comments to be displayed in the comment * block. * * @ingroup themeable */ function theme_comment_block() { $items = array(); foreach (comment_get_recent() as $comment) { $items[] = l($comment->subject, 'node/'. $comment->nid, NULL, NULL, 'comment-'. $comment->cid) .'
    '. t('@time ago', array('@time' => format_interval(time() - $comment->timestamp))); } if ($items) { return theme('item_list', $items); } } /** * Implementation of hook_link(). */ function comment_link($type, $node = NULL, $teaser = FALSE) { $links = array(); if ($type == 'node' && $node->comment) { if ($teaser) { // Main page: display the number of comments that have been posted. if (user_access('access comments')) { $all = comment_num_all($node->nid); if ($all) { $links['comment_comments'] = array( 'title' => format_plural($all, '1 comment', '@count comments'), 'href' => "node/$node->nid", 'attributes' => array('title' => t('Jump to the first comment of this posting.')), 'fragment' => 'comments' ); $new = comment_num_new($node->nid); if ($new) { $links['comment_new_comments'] = array( 'title' => format_plural($new, '1 new comment', '@count new comments'), 'href' => "node/$node->nid", 'attributes' => array('title' => t('Jump to the first new comment of this posting.')), 'fragment' => 'new' ); } } else { if ($node->comment == COMMENT_NODE_READ_WRITE) { if (user_access('post comments')) { $links['comment_add'] = array( 'title' => t('Add new comment'), 'href' => "comment/reply/$node->nid", 'attributes' => array('title' => t('Add a new comment to this page.')), 'fragment' => 'comment-form' ); } else { $links['comment_forbidden']['title'] = theme('comment_post_forbidden', $node->nid); } } } } } else { // Node page: add a "post comment" link if the user is allowed to // post comments, if this node is not read-only, and if the comment form isn't already shown if ($node->comment == COMMENT_NODE_READ_WRITE) { if (user_access('post comments')) { if (variable_get('comment_form_location', COMMENT_FORM_SEPARATE_PAGE) == COMMENT_FORM_SEPARATE_PAGE) { $links['comment_add'] = array( 'title' => t('Add new comment'), 'href' => "comment/reply/$node->nid", 'attributes' => array('title' => t('Share your thoughts and opinions related to this posting.')), 'fragment' => 'comment-form' ); } } else { $links['comment_forbidden']['title'] = theme('comment_post_forbidden', $node->nid); } } } } if ($type == 'comment') { $links = comment_links($node, $teaser); } if (isset($links['comment_forbidden'])) { $links['comment_forbidden']['html'] = TRUE; } return $links; } function comment_form_alter($form_id, &$form) { if ($form_id == 'node_type_form' && isset($form['identity']['type'])) { $form['workflow']['comment'] = array( '#type' => 'radios', '#title' => t('Default comment setting'), '#default_value' => variable_get('comment_'. $form['#node_type']->type, COMMENT_NODE_READ_WRITE), '#options' => array(t('Disabled'), t('Read only'), t('Read/Write')), '#description' => t('Users with the administer comments permission will be able to override this setting.'), ); } elseif (isset($form['type'])) { if ($form['type']['#value'] .'_node_form' == $form_id) { $node = $form['#node']; $form['comment_settings'] = array( '#type' => 'fieldset', '#access' => user_access('administer comments'), '#title' => t('Comment settings'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#weight' => 30, ); $form['comment_settings']['comment'] = array( '#type' => 'radios', '#parents' => array('comment'), '#default_value' => $node->comment, '#options' => array(t('Disabled'), t('Read only'), t('Read/Write')), ); } } } /** * Implementation of hook_nodeapi(). * */ function comment_nodeapi(&$node, $op, $arg = 0) { switch ($op) { case 'load': return db_fetch_array(db_query("SELECT last_comment_timestamp, last_comment_name, comment_count FROM {node_comment_statistics} WHERE nid = %d", $node->nid)); break; case 'prepare': if (!isset($node->comment)) { $node->comment = variable_get("comment_$node->type", COMMENT_NODE_READ_WRITE); } break; case 'insert': db_query('INSERT INTO {node_comment_statistics} (nid, last_comment_timestamp, last_comment_name, last_comment_uid, comment_count) VALUES (%d, %d, NULL, %d, 0)', $node->nid, $node->changed, $node->uid); break; case 'delete': db_query('DELETE FROM {comments} WHERE nid = %d', $node->nid); db_query('DELETE FROM {node_comment_statistics} WHERE nid = %d', $node->nid); break; case 'update index': $text = ''; $comments = db_query('SELECT subject, comment, format FROM {comments} WHERE nid = %d AND status = %d', $node->nid, COMMENT_PUBLISHED); while ($comment = db_fetch_object($comments)) { $text .= '

    '. check_plain($comment->subject) .'

    '. check_markup($comment->comment, $comment->format, FALSE); } return $text; case 'search result': $comments = db_result(db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = %d', $node->nid)); return format_plural($comments, '1 comment', '@count comments'); case 'rss item': if ($node->comment != COMMENT_NODE_DISABLED) { return array(array('key' => 'comments', 'value' => url('node/'. $node->nid, NULL, 'comments', TRUE))); } else { return array(); } } } /** * Implementation of hook_user(). * * Provides signature customization for the user's comments. */ function comment_user($type, $edit, &$user, $category = NULL) { if ($type == 'form' && $category == 'account') { // when user tries to edit his own data $form['comment_settings'] = array( '#type' => 'fieldset', '#title' => t('Comment settings'), '#collapsible' => TRUE, '#weight' => 4); $form['comment_settings']['signature'] = array( '#type' => 'textarea', '#title' => t('Signature'), '#default_value' => $edit['signature'], '#description' => t('Your signature will be publicly displayed at the end of your comments.')); return $form; } elseif ($type == 'delete') { db_query('UPDATE {comments} SET uid = 0 WHERE uid = %d', $user->uid); db_query('UPDATE {node_comment_statistics} SET last_comment_uid = 0 WHERE last_comment_uid = %d', $user->uid); } } /** * Menu callback; presents the comment settings page. */ function comment_admin_settings() { $form['viewing_options'] = array( '#type' => 'fieldset', '#title' => t('Viewing options'), '#collapsible' => TRUE, ); $form['viewing_options']['comment_default_mode'] = array( '#type' => 'radios', '#title' => t('Default display mode'), '#default_value' => variable_get('comment_default_mode', COMMENT_MODE_THREADED_EXPANDED), '#options' => _comment_get_modes(), '#description' => t('The default view for comments. Expanded views display the body of the comment. Threaded views keep replies together.'), ); $form['viewing_options']['comment_default_order'] = array( '#type' => 'radios', '#title' => t('Default display order'), '#default_value' => variable_get('comment_default_order', COMMENT_ORDER_NEWEST_FIRST), '#options' => _comment_get_orders(), '#description' => t('The default sorting for new users and anonymous users while viewing comments. These users may change their view using the comment control panel. For registered users, this change is remembered as a persistent user preference.'), ); $form['viewing_options']['comment_default_per_page'] = array( '#type' => 'select', '#title' => t('Default comments per page'), '#default_value' => variable_get('comment_default_per_page', 50), '#options' => _comment_per_page(), '#description' => t('Default number of comments for each page: more comments are distributed in several pages.'), ); $form['viewing_options']['comment_controls'] = array( '#type' => 'radios', '#title' => t('Comment controls'), '#default_value' => variable_get('comment_controls', COMMENT_CONTROLS_HIDDEN), '#options' => array( t('Display above the comments'), t('Display below the comments'), t('Display above and below the comments'), t('Do not display')), '#description' => t('Position of the comment controls box. The comment controls let the user change the default display mode and display order of comments.'), ); $form['posting_settings'] = array( '#type' => 'fieldset', '#title' => t('Posting settings'), '#collapsible' => TRUE, ); $form['posting_settings']['comment_anonymous'] = array( '#type' => 'radios', '#title' => t('Anonymous commenting'), '#default_value' => variable_get('comment_anonymous', COMMENT_ANONYMOUS_MAYNOT_CONTACT), '#options' => array( COMMENT_ANONYMOUS_MAYNOT_CONTACT => t('Anonymous posters may not enter their contact information'), COMMENT_ANONYMOUS_MAY_CONTACT => t('Anonymous posters may leave their contact information'), COMMENT_ANONYMOUS_MUST_CONTACT => t('Anonymous posters must leave their contact information')), '#description' => t('This option is enabled when anonymous users have permission to post comments on the permissions page.', array('@url' => url('admin/user/access', NULL, 'module-comment'))), ); if (!user_access('post comments', user_load(array('uid' => 0)))) { $form['posting_settings']['comment_anonymous']['#disabled'] = TRUE; } $form['posting_settings']['comment_subject_field'] = array( '#type' => 'radios', '#title' => t('Comment subject field'), '#default_value' => variable_get('comment_subject_field', 1), '#options' => array(t('Disabled'), t('Enabled')), '#description' => t('Can users provide a unique subject for their comments?'), ); $form['posting_settings']['comment_preview'] = array( '#type' => 'radios', '#title' => t('Preview comment'), '#default_value' => variable_get('comment_preview', COMMENT_PREVIEW_REQUIRED), '#options' => array(t('Optional'), t('Required')), ); $form['posting_settings']['comment_form_location'] = array( '#type' => 'radios', '#title' => t('Location of comment submission form'), '#default_value' => variable_get('comment_form_location', COMMENT_FORM_SEPARATE_PAGE), '#options' => array(t('Display on separate page'), t('Display below post or comments')), ); return system_settings_form($form); } /** * This is *not* a hook_access() implementation. This function is called * to determine whether the current user has access to a particular comment. * * Authenticated users can edit their comments as long they have not been * replied to. This prevents people from changing or revising their * statements based on the replies to their posts. */ function comment_access($op, $comment) { global $user; if ($op == 'edit') { return ($user->uid && $user->uid == $comment->uid && comment_num_replies($comment->cid) == 0) || user_access('administer comments'); } } function comment_node_url() { return arg(0) .'/'. arg(1); } function comment_edit($cid) { global $user; $comment = db_fetch_object(db_query('SELECT c.*, u.uid, u.name AS registered_name, u.data FROM {comments} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.cid = %d', $cid)); $comment = drupal_unpack($comment); $comment->name = $comment->uid ? $comment->registered_name : $comment->name; if (comment_access('edit', $comment)) { return comment_form_box((array)$comment); } else { drupal_access_denied(); } } /** * This function is responsible for generating a comment reply form. * There are several cases that have to be handled, including: * - replies to comments * - replies to nodes * - attempts to reply to nodes that can no longer accept comments * - respecting access permissions ('access comments', 'post comments', etc.) * * The node or comment that is being replied to must appear above the comment * form to provide the user context while authoring the comment. * * @param $nid * Every comment belongs to a node. This is that node's id. * @param $pid * Some comments are replies to other comments. In those cases, $pid is the parent * comment's cid. * * @return $output * The rendered parent node or comment plus the new comment form. */ function comment_reply($nid, $pid = NULL) { // Load the parent node. $node = node_load($nid); // Set the breadcrumb trail. menu_set_location(array(array('path' => "node/$nid", 'title' => $node->title), array('path' => "comment/reply/$nid"))); $op = isset($_POST['op']) ? $_POST['op'] : ''; $output = ''; if (user_access('access comments')) { // The user is previewing a comment prior to submitting it. if ($op == t('Preview comment')) { if (user_access('post comments')) { $output .= comment_form_box(array('pid' => $pid, 'nid' => $nid), NULL); } else { drupal_set_message(t('You are not authorized to post comments.'), 'error'); drupal_goto("node/$nid"); } } else { // $pid indicates that this is a reply to a comment. if ($pid) { // load the comment whose cid = $pid if ($comment = db_fetch_object(db_query('SELECT c.*, u.uid, u.name AS registered_name, u.picture, u.data FROM {comments} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.cid = %d AND c.status = %d', $pid, COMMENT_PUBLISHED))) { // If that comment exists, make sure that the current comment and the parent comment both // belong to the same parent node. if ($comment->nid != $nid) { // Attempting to reply to a comment not belonging to the current nid. drupal_set_message(t('The comment you are replying to does not exist.'), 'error'); drupal_goto("node/$nid"); } // Display the parent comment $comment = drupal_unpack($comment); $comment->name = $comment->uid ? $comment->registered_name : $comment->name; $output .= theme('comment_view', $comment); } else { drupal_set_message(t('The comment you are replying to does not exist.'), 'error'); drupal_goto("node/$nid"); } } // This is the case where the comment is in response to a node. Display the node. else if (user_access('access content')) { $output .= node_view($node); } // Should we show the reply box? if (node_comment_mode($nid) != COMMENT_NODE_READ_WRITE) { drupal_set_message(t("This discussion is closed: you can't post new comments."), 'error'); drupal_goto("node/$nid"); } else if (user_access('post comments')) { $output .= comment_form_box(array('pid' => $pid, 'nid' => $nid), t('Reply')); } else { drupal_set_message(t('You are not authorized to post comments.'), 'error'); drupal_goto("node/$nid"); } } } else { drupal_set_message(t('You are not authorized to view comments.'), 'error'); drupal_goto("node/$nid"); } return $output; } /** * Accepts a submission of new or changed comment content. * * @param $edit * A comment array. * * @return * If the comment is successfully saved the comment ID is returned. If the comment * is not saved, FALSE is returned. */ function comment_save($edit) { global $user; if (user_access('post comments') && (user_access('administer comments') || node_comment_mode($edit['nid']) == COMMENT_NODE_READ_WRITE)) { if (!form_get_errors()) { if ($edit['cid']) { // Update the comment in the database. db_query("UPDATE {comments} SET status = %d, timestamp = %d, subject = '%s', comment = '%s', format = %d, uid = %d, name = '%s', mail = '%s', homepage = '%s' WHERE cid = %d", $edit['status'], $edit['timestamp'], $edit['subject'], $edit['comment'], $edit['format'], $edit['uid'], $edit['name'], $edit['mail'], $edit['homepage'], $edit['cid']); _comment_update_node_statistics($edit['nid']); // Allow modules to respond to the updating of a comment. comment_invoke_comment($edit, 'update'); // Add an entry to the watchdog log. watchdog('content', t('Comment: updated %subject.', array('%subject' => $edit['subject'])), WATCHDOG_NOTICE, l(t('view'), 'node/'. $edit['nid'], NULL, NULL, 'comment-'. $edit['cid'])); } else { // Check for duplicate comments. Note that we have to use the // validated/filtered data to perform such check. $duplicate = db_result(db_query("SELECT COUNT(cid) FROM {comments} WHERE pid = %d AND nid = %d AND subject = '%s' AND comment = '%s'", $edit['pid'], $edit['nid'], $edit['subject'], $edit['comment']), 0); if ($duplicate != 0) { watchdog('content', t('Comment: duplicate %subject.', array('%subject' => $edit['subject'])), WATCHDOG_WARNING); } // Add the comment to database. $edit['status'] = user_access('post comments without approval') ? COMMENT_PUBLISHED : COMMENT_NOT_PUBLISHED; $roles = variable_get('comment_roles', array()); $score = 0; foreach (array_intersect(array_keys($roles), array_keys($user->roles)) as $rid) { $score = max($roles[$rid], $score); } $users = serialize(array(0 => $score)); // Here we are building the thread field. See the documentation for // comment_render(). if ($edit['pid'] == 0) { // This is a comment with no parent comment (depth 0): we start // by retrieving the maximum thread level. $max = db_result(db_query('SELECT MAX(thread) FROM {comments} WHERE nid = %d', $edit['nid'])); // Strip the "/" from the end of the thread. $max = rtrim($max, '/'); // Finally, build the thread field for this new comment. $thread = int2vancode(vancode2int($max) + 1) .'/'; } else { // This is comment with a parent comment: we increase // the part of the thread value at the proper depth. // Get the parent comment: $parent = _comment_load($edit['pid']); // Strip the "/" from the end of the parent thread. $parent->thread = (string) rtrim((string) $parent->thread, '/'); // Get the max value in _this_ thread. $max = db_result(db_query("SELECT MAX(thread) FROM {comments} WHERE thread LIKE '%s.%%' AND nid = %d", $parent->thread, $edit['nid'])); if ($max == '') { // First child of this parent. $thread = $parent->thread .'.'. int2vancode(0) .'/'; } else { // Strip the "/" at the end of the thread. $max = rtrim($max, '/'); // We need to get the value at the correct depth. $parts = explode('.', $max); $parent_depth = count(explode('.', $parent->thread)); $last = $parts[$parent_depth]; // Finally, build the thread field for this new comment. $thread = $parent->thread .'.'. int2vancode(vancode2int($last) + 1) .'/'; } } $edit['cid'] = db_next_id('{comments}_cid'); $edit['timestamp'] = time(); if ($edit['uid'] === $user->uid) { // '===' because we want to modify anonymous users too $edit['name'] = $user->name; } db_query("INSERT INTO {comments} (cid, nid, pid, uid, subject, comment, format, hostname, timestamp, status, score, users, thread, name, mail, homepage) VALUES (%d, %d, %d, %d, '%s', '%s', %d, '%s', %d, %d, %d, '%s', '%s', '%s', '%s', '%s')", $edit['cid'], $edit['nid'], $edit['pid'], $edit['uid'], $edit['subject'], $edit['comment'], $edit['format'], $_SERVER['REMOTE_ADDR'], $edit['timestamp'], $edit['status'], $score, $users, $thread, $edit['name'], $edit['mail'], $edit['homepage']); _comment_update_node_statistics($edit['nid']); // Tell the other modules a new comment has been submitted. comment_invoke_comment($edit, 'insert'); // Add an entry to the watchdog log. watchdog('content', t('Comment: added %subject.', array('%subject' => $edit['subject'])), WATCHDOG_NOTICE, l(t('view'), 'node/'. $edit['nid'], NULL, NULL, 'comment-'. $edit['cid'])); } // Clear the cache so an anonymous user can see his comment being added. cache_clear_all(); // Explain the approval queue if necessary, and then // redirect the user to the node he's commenting on. if ($edit['status'] == COMMENT_NOT_PUBLISHED) { drupal_set_message(t('Your comment has been queued for moderation by site administrators and will be published after approval.')); } return $edit['cid']; } else { return FALSE; } } else { $txt = t('Comment: unauthorized comment submitted or comment submitted to a closed node %subject.', array('%subject' => $edit['subject'])); watchdog('content', $txt, WATCHDOG_WARNING); drupal_set_message($txt, 'error'); return FALSE; } } function comment_links($comment, $return = 1) { global $user; $links = array(); // If we are viewing just this comment, we link back to the node. if ($return) { $links['comment_parent'] = array( 'title' => t('parent'), 'href' => comment_node_url(), 'fragment' => "comment-$comment->cid" ); } if (node_comment_mode($comment->nid) == COMMENT_NODE_READ_WRITE) { if (user_access('administer comments') && user_access('post comments')) { $links['comment_delete'] = array( 'title' => t('delete'), 'href' => "comment/delete/$comment->cid" ); $links['comment_edit'] = array( 'title' => t('edit'), 'href' => "comment/edit/$comment->cid" ); $links['comment_reply'] = array( 'title' => t('reply'), 'href' => "comment/reply/$comment->nid/$comment->cid" ); } else if (user_access('post comments')) { if (comment_access('edit', $comment)) { $links['comment_edit'] = array( 'title' => t('edit'), 'href' => "comment/edit/$comment->cid" ); } $links['comment_reply'] = array( 'title' => t('reply'), 'href' => "comment/reply/$comment->nid/$comment->cid" ); } else { $links['comment_forbidden']['title'] = theme('comment_post_forbidden', $comment->nid); } } return $links; } /** * Renders comment(s). * * @param $node * The node which comment(s) needs rendering. * @param $cid * Optional, if given, only one comment is rendered. * * To display threaded comments in the correct order we keep a 'thread' field * and order by that value. This field keeps this data in * a way which is easy to update and convenient to use. * * A "thread" value starts at "1". If we add a child (A) to this comment, * we assign it a "thread" = "1.1". A child of (A) will have "1.1.1". Next * brother of (A) will get "1.2". Next brother of the parent of (A) will get * "2" and so on. * * First of all note that the thread field stores the depth of the comment: * depth 0 will be "X", depth 1 "X.X", depth 2 "X.X.X", etc. * * Now to get the ordering right, consider this example: * * 1 * 1.1 * 1.1.1 * 1.2 * 2 * * If we "ORDER BY thread ASC" we get the above result, and this is the * natural order sorted by time. However, if we "ORDER BY thread DESC" * we get: * * 2 * 1.2 * 1.1.1 * 1.1 * 1 * * Clearly, this is not a natural way to see a thread, and users will get * confused. The natural order to show a thread by time desc would be: * * 2 * 1 * 1.2 * 1.1 * 1.1.1 * * which is what we already did before the standard pager patch. To achieve * this we simply add a "/" at the end of each "thread" value. This way out * thread fields will look like depicted below: * * 1/ * 1.1/ * 1.1.1/ * 1.2/ * 2/ * * we add "/" since this char is, in ASCII, higher than every number, so if * now we "ORDER BY thread DESC" we get the correct order. However this would * spoil the reverse ordering, "ORDER BY thread ASC" -- here, we do not need * to consider the trailing "/" so we use a substring only. */ function comment_render($node, $cid = 0) { global $user; $output = ''; if (user_access('access comments')) { // Pre-process variables. $nid = $node->nid; if (empty($nid)) { $nid = 0; } $mode = _comment_get_display_setting('mode'); $order = _comment_get_display_setting('sort'); $comments_per_page = _comment_get_display_setting('comments_per_page'); if ($cid) { // Single comment view. $query = 'SELECT c.cid, c.pid, c.nid, c.subject, c.comment, c.format, c.timestamp, c.name, c.mail, c.homepage, u.uid, u.name AS registered_name, u.picture, u.data, c.score, c.users, c.status FROM {comments} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.cid = %d'; $query_args = array($cid); if (!user_access('administer comments')) { $query .= ' AND c.status = %d'; $query_args[] = COMMENT_PUBLISHED; } $result = db_query($query, $query_args); if ($comment = db_fetch_object($result)) { $comment->name = $comment->uid ? $comment->registered_name : $comment->name; $links = module_invoke_all('link', 'comment', $comment, 1); foreach (module_implements('link_alter') as $module) { $function = $module .'_link_alter'; $function($node, $links); } $output .= theme('comment_view', $comment, $links); } } else { // Multiple comment view $query_count = 'SELECT COUNT(*) FROM {comments} WHERE nid = %d'; $query = 'SELECT c.cid as cid, c.pid, c.nid, c.subject, c.comment, c.format, c.timestamp, c.name, c.mail, c.homepage, u.uid, u.name AS registered_name, u.picture, u.data, c.score, c.users, c.thread, c.status FROM {comments} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.nid = %d'; $query_args = array($nid); if (!user_access('administer comments')) { $query .= ' AND c.status = %d'; $query_count .= ' AND status = %d'; $query_args[] = COMMENT_PUBLISHED; } if ($order == COMMENT_ORDER_NEWEST_FIRST) { if ($mode == COMMENT_MODE_FLAT_COLLAPSED || $mode == COMMENT_MODE_FLAT_EXPANDED) { $query .= ' ORDER BY c.cid DESC'; } else { $query .= ' ORDER BY c.thread DESC'; } } else if ($order == COMMENT_ORDER_OLDEST_FIRST) { if ($mode == COMMENT_MODE_FLAT_COLLAPSED || $mode == COMMENT_MODE_FLAT_EXPANDED) { $query .= ' ORDER BY c.cid'; } else { /* ** See comment above. Analysis learns that this doesn't cost ** too much. It scales much much better than having the whole ** comment structure. */ $query .= ' ORDER BY SUBSTRING(c.thread, 1, (LENGTH(c.thread) - 1))'; } } // Start a form, for use with comment control. $result = pager_query($query, $comments_per_page, 0, $query_count, $query_args); if (db_num_rows($result) && (variable_get('comment_controls', COMMENT_CONTROLS_HIDDEN) == COMMENT_CONTROLS_ABOVE || variable_get('comment_controls', COMMENT_CONTROLS_HIDDEN) == COMMENT_CONTROLS_ABOVE_BELOW)) { $output .= drupal_get_form('comment_controls', $mode, $order, $comments_per_page); } $divs = 0; $last_depth = 0; drupal_add_css(drupal_get_path('module', 'comment') .'/comment.css'); while ($comment = db_fetch_object($result)) { $comment = drupal_unpack($comment); $comment->name = $comment->uid ? $comment->registered_name : $comment->name; $comment->depth = count(explode('.', $comment->thread)) - 1; if ($mode == COMMENT_MODE_THREADED_COLLAPSED || $mode == COMMENT_MODE_THREADED_EXPANDED) { if ($comment->depth > $last_depth) { $divs++; $output .= '
    '; $last_depth++; } else { while ($comment->depth < $last_depth) { $divs--; $output .= '
    '; $last_depth--; } } } if ($mode == COMMENT_MODE_FLAT_COLLAPSED) { $output .= theme('comment_flat_collapsed', $comment); } else if ($mode == COMMENT_MODE_FLAT_EXPANDED) { $output .= theme('comment_flat_expanded', $comment); } else if ($mode == COMMENT_MODE_THREADED_COLLAPSED) { $output .= theme('comment_thread_collapsed', $comment); } else if ($mode == COMMENT_MODE_THREADED_EXPANDED) { $output .= theme('comment_thread_expanded', $comment); } } for ($i = 0; $i < $divs; $i++) { $output .= ''; } $output .= theme('pager', NULL, $comments_per_page, 0); if (db_num_rows($result) && (variable_get('comment_controls', COMMENT_CONTROLS_HIDDEN) == COMMENT_CONTROLS_BELOW || variable_get('comment_controls', COMMENT_CONTROLS_HIDDEN) == COMMENT_CONTROLS_ABOVE_BELOW)) { $output .= drupal_get_form('comment_controls', $mode, $order, $comments_per_page); } } // If enabled, show new comment form if it's not already being displayed. $reply = arg(0) == 'comment' && arg(1) == 'reply'; if (user_access('post comments') && node_comment_mode($nid) == COMMENT_NODE_READ_WRITE && (variable_get('comment_form_location', COMMENT_FORM_SEPARATE_PAGE) == COMMENT_FORM_BELOW) && !$reply) { $output .= comment_form_box(array('nid' => $nid), t('Post new comment')); } $output = theme('comment_wrapper', $output); } return $output; } /** * Menu callback; delete a comment. */ function comment_delete($cid = NULL) { $comment = db_fetch_object(db_query('SELECT c.*, u.name AS registered_name, u.uid FROM {comments} c INNER JOIN {users} u ON u.uid = c.uid WHERE c.cid = %d', $cid)); $comment->name = $comment->uid ? $comment->registered_name : $comment->name; $output = ''; if (is_object($comment) && is_numeric($comment->cid)) { $output = drupal_get_form('comment_confirm_delete', $comment); } else { drupal_set_message(t('The comment no longer exists.')); } return $output; } function comment_confirm_delete($comment) { $form = array(); $form['comment'] = array( '#type' => 'value', '#value' => $comment, ); return confirm_form( $form, t('Are you sure you want to delete the comment %title?', array('%title' => $comment->subject)), 'node/'. $comment->nid, t('Any replies to this comment will be lost. This action cannot be undone.'), t('Delete'), t('Cancel')); } function comment_confirm_delete_submit($form_id, $form_values) { $comment = $form_values['comment']; // Delete comment and its replies. _comment_delete_thread($comment); _comment_update_node_statistics($comment->nid); // Clear the cache so an anonymous user sees that his comment was deleted. cache_clear_all(); drupal_set_message(t('The comment and all its replies have been deleted.')); return "node/$comment->nid"; } /** * Comment operations. We offer different update operations depending on * which comment administration page we're on. */ function comment_operations($action = NULL) { if ($action == 'publish') { $operations = array( 'publish' => array(t('Publish the selected comments'), 'UPDATE {comments} SET status = '. COMMENT_PUBLISHED .' WHERE cid = %d'), 'delete' => array(t('Delete the selected comments'), '') ); } else if ($action == 'unpublish') { $operations = array( 'unpublish' => array(t('Unpublish the selected comments'), 'UPDATE {comments} SET status = '. COMMENT_NOT_PUBLISHED .' WHERE cid = %d'), 'delete' => array(t('Delete the selected comments'), '') ); } else { $operations = array( 'publish' => array(t('Publish the selected comments'), 'UPDATE {comments} SET status = '. COMMENT_PUBLISHED .' WHERE cid = %d'), 'unpublish' => array(t('Unpublish the selected comments'), 'UPDATE {comments} SET status = '. COMMENT_NOT_PUBLISHED .' WHERE cid = %d'), 'delete' => array(t('Delete the selected comments'), '') ); } return $operations; } /** * Menu callback; present an administrative comment listing. */ function comment_admin($type = 'new') { $edit = $_POST; if ($edit['operation'] == 'delete' && $edit['comments']) { return drupal_get_form('comment_multiple_delete_confirm'); } else { return drupal_get_form('comment_admin_overview', $type, arg(4)); } } function comment_admin_overview($type = 'new', $arg) { // build an 'Update options' form $form['options'] = array( '#type' => 'fieldset', '#title' => t('Update options'), '#prefix' => '
    ', '#suffix' => '
    ' ); $options = array(); foreach (comment_operations($arg == 'approval' ? 'publish' : 'unpublish') as $key => $value) { $options[$key] = $value[0]; } $form['options']['operation'] = array('#type' => 'select', '#options' => $options, '#default_value' => 'publish'); $form['options']['submit'] = array('#type' => 'submit', '#value' => t('Update')); // load the comments that we want to display $status = ($type == 'approval') ? COMMENT_NOT_PUBLISHED : COMMENT_PUBLISHED; $form['header'] = array('#type' => 'value', '#value' => array( theme('table_select_header_cell'), array('data' => t('Subject'), 'field' => 'subject'), array('data' => t('Author'), 'field' => 'name'), array('data' => t('Time'), 'field' => 'timestamp', 'sort' => 'desc'), array('data' => t('Operations')) )); $result = pager_query('SELECT c.subject, c.nid, c.cid, c.comment, c.timestamp, c.status, c.name, c.homepage, u.name AS registered_name, u.uid FROM {comments} c INNER JOIN {users} u ON u.uid = c.uid WHERE c.status = %d'. tablesort_sql($form['header']['#value']), 50, 0, NULL, $status); // build a table listing the appropriate comments $destination = drupal_get_destination(); while ($comment = db_fetch_object($result)) { $comments[$comment->cid] = ''; $comment->name = $comment->uid ? $comment->registered_name : $comment->name; $form['subject'][$comment->cid] = array('#value' => l($comment->subject, 'node/'. $comment->nid, array('title' => truncate_utf8($comment->comment, 128)), NULL, 'comment-'. $comment->cid)); $form['username'][$comment->cid] = array('#value' => theme('username', $comment)); $form['timestamp'][$comment->cid] = array('#value' => format_date($comment->timestamp, 'small')); $form['operations'][$comment->cid] = array('#value' => l(t('edit'), 'comment/edit/'. $comment->cid, array(), $destination)); } $form['comments'] = array('#type' => 'checkboxes', '#options' => $comments); $form['pager'] = array('#value' => theme('pager', NULL, 50, 0)); return $form; } /** * We can't execute any 'Update options' if no comments were selected. */ function comment_admin_overview_validate($form_id, $form_values) { $form_values['comments'] = array_diff($form_values['comments'], array(0)); if (count($form_values['comments']) == 0) { form_set_error('', t('Please select one or more comments to perform the update on.')); drupal_goto('admin/content/comment'); } } /** * Execute the chosen 'Update option' on the selected comments, such as * publishing, unpublishing or deleting. */ function comment_admin_overview_submit($form_id, $form_values) { $operations = comment_operations(); if ($operations[$form_values['operation']][1]) { // extract the appropriate database query operation $query = $operations[$form_values['operation']][1]; foreach ($form_values['comments'] as $cid => $value) { if ($value) { // perform the update action, then refresh node statistics db_query($query, $cid); $comment = _comment_load($cid); _comment_update_node_statistics($comment->nid); // Allow modules to respond to the updating of a comment. comment_invoke_comment($comment, $form_values['operation']); // Add an entry to the watchdog log. watchdog('content', t('Comment: updated %subject.', array('%subject' => $comment->subject)), WATCHDOG_NOTICE, l(t('view'), 'node/'. $comment->nid, NULL, NULL, 'comment-'. $comment->cid)); } } cache_clear_all(); drupal_set_message(t('The update has been performed.')); return 'admin/content/comment'; } } function theme_comment_admin_overview($form) { $output = drupal_render($form['options']); if (isset($form['subject']) && is_array($form['subject'])) { foreach (element_children($form['subject']) as $key) { $row = array(); $row[] = drupal_render($form['comments'][$key]); $row[] = drupal_render($form['subject'][$key]); $row[] = drupal_render($form['username'][$key]); $row[] = drupal_render($form['timestamp'][$key]); $row[] = drupal_render($form['operations'][$key]); $rows[] = $row; } } else { $rows[] = array(array('data' => t('No comments available.'), 'colspan' => '6')); } $output .= theme('table', $form['header']['#value'], $rows); if ($form['pager']['#value']) { $output .= drupal_render($form['pager']); } $output .= drupal_render($form); return $output; } /** * List the selected comments and verify that the admin really wants to delete * them. */ function comment_multiple_delete_confirm() { $edit = $_POST; $form['comments'] = array('#prefix' => '
      ', '#suffix' => '
    ', '#tree' => TRUE); // array_filter() returns only elements with actual values $comment_counter = 0; foreach (array_filter($edit['comments']) as $cid => $value) { $comment = _comment_load($cid); if (is_object($comment) && is_numeric($comment->cid)) { $subject = db_result(db_query('SELECT subject FROM {comments} WHERE cid = %d', $cid)); $form['comments'][$cid] = array('#type' => 'hidden', '#value' => $cid, '#prefix' => '
  • ', '#suffix' => check_plain($subject) .'
  • '); $comment_counter++; } } $form['operation'] = array('#type' => 'hidden', '#value' => 'delete'); if (!$comment_counter) { drupal_set_message(t('There do not appear to be any comments to delete or your selected comment was deleted by another administrator.')); drupal_goto('admin/content/comment'); } else { return confirm_form($form, t('Are you sure you want to delete these comments and all their children?'), 'admin/content/comment', t('This action cannot be undone.'), t('Delete comments'), t('Cancel')); } } /** * Perform the actual comment deletion. */ function comment_multiple_delete_confirm_submit($form_id, $form_values) { if ($form_values['confirm']) { foreach ($form_values['comments'] as $cid => $value) { $comment = _comment_load($cid); _comment_delete_thread($comment); _comment_update_node_statistics($comment->nid); } cache_clear_all(); drupal_set_message(t('The comments have been deleted.')); } drupal_goto('admin/content/comment'); } /** *** misc functions: helpers, privates, history **/ /** * Load the entire comment by cid. */ function _comment_load($cid) { return db_fetch_object(db_query('SELECT * FROM {comments} WHERE cid = %d', $cid)); } function comment_num_all($nid) { static $cache; if (!isset($cache[$nid])) { $cache[$nid] = db_result(db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = %d', $nid)); } return $cache[$nid]; } function comment_num_replies($pid) { static $cache; if (!isset($cache[$pid])) { $cache[$pid] = db_result(db_query('SELECT COUNT(cid) FROM {comments} WHERE pid = %d AND status = %d', $pid, COMMENT_PUBLISHED)); } return $cache[$pid]; } /** * get number of new comments for current user and specified node * * @param $nid node-id to count comments for * @param $timestamp time to count from (defaults to time of last user access * to node) */ function comment_num_new($nid, $timestamp = 0) { global $user; if ($user->uid) { // Retrieve the timestamp at which the current user last viewed the // specified node. if (!$timestamp) { $timestamp = node_last_viewed($nid); } $timestamp = ($timestamp > NODE_NEW_LIMIT ? $timestamp : NODE_NEW_LIMIT); // Use the timestamp to retrieve the number of new comments. $result = db_result(db_query('SELECT COUNT(c.cid) FROM {node} n INNER JOIN {comments} c ON n.nid = c.nid WHERE n.nid = %d AND timestamp > %d AND c.status = %d', $nid, $timestamp, COMMENT_PUBLISHED)); return $result; } else { return 0; } } function comment_validate($edit) { global $user; // Invoke other validation handlers comment_invoke_comment($edit, 'validate'); if (isset($edit['date'])) { // As of PHP 5.1.0, strtotime returns FALSE upon failure instead of -1. if (strtotime($edit['date']) <= 0) { form_set_error('date', t('You have to specify a valid date.')); } } if (isset($edit['author']) && !$account = user_load(array('name' => $edit['author']))) { form_set_error('author', t('You have to specify a valid author.')); } // Check validity of name, mail and homepage (if given) if (!$user->uid || isset($edit['is_anonymous'])) { if (variable_get('comment_anonymous', COMMENT_ANONYMOUS_MAYNOT_CONTACT) > COMMENT_ANONYMOUS_MAYNOT_CONTACT) { if ($edit['name']) { $taken = db_result(db_query("SELECT COUNT(uid) FROM {users} WHERE LOWER(name) = '%s'", $edit['name']), 0); if ($taken != 0) { form_set_error('name', t('The name you used belongs to a registered user.')); } } else if (variable_get('comment_anonymous', COMMENT_ANONYMOUS_MAYNOT_CONTACT) == COMMENT_ANONYMOUS_MUST_CONTACT) { form_set_error('name', t('You have to leave your name.')); } if ($edit['mail']) { if (!valid_email_address($edit['mail'])) { form_set_error('mail', t('The e-mail address you specified is not valid.')); } } else if (variable_get('comment_anonymous', COMMENT_ANONYMOUS_MAYNOT_CONTACT) == COMMENT_ANONYMOUS_MUST_CONTACT) { form_set_error('mail', t('You have to leave an e-mail address.')); } if ($edit['homepage']) { if (!valid_url($edit['homepage'], TRUE)) { form_set_error('homepage', t('The URL of your homepage is not valid. Remember that it must be fully qualified, i.e. of the form http://example.com/directory.')); } } } } return $edit; } /* ** Generate the basic commenting form, for appending to a node or display on a separate page. ** This is rendered by theme_comment_form. */ function comment_form($edit, $title = NULL) { global $user; $op = isset($_POST['op']) ? $_POST['op'] : ''; if ($user->uid) { if ($edit['cid'] && user_access('administer comments')) { if ($edit['author']) { $author = $edit['author']; } elseif ($edit['name']) { $author = $edit['name']; } else { $author = $edit['registered_name']; } if ($edit['status']) { $status = $edit['status']; } else { $status = 0; } if ($edit['date']) { $date = $edit['date']; } else { $date = format_date($edit['timestamp'], 'custom', 'Y-m-d H:i O'); } $form['admin'] = array( '#type' => 'fieldset', '#title' => t('Administration'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#weight' => -2, ); if ($edit['registered_name'] != '') { // The comment is by a registered user $form['admin']['author'] = array( '#type' => 'textfield', '#title' => t('Authored by'), '#size' => 30, '#maxlength' => 60, '#autocomplete_path' => 'user/autocomplete', '#default_value' => $author, '#weight' => -1, ); } else { // The comment is by an anonymous user $form['is_anonymous'] = array( '#type' => 'value', '#value' => TRUE, ); $form['admin']['name'] = array( '#type' => 'textfield', '#title' => t('Authored by'), '#size' => 30, '#maxlength' => 60, '#default_value' => $author, '#weight' => -1, ); $form['admin']['mail'] = array( '#type' => 'textfield', '#title' => t('E-mail'), '#maxlength' => 64, '#size' => 30, '#default_value' => $edit['mail'], '#description' => t('The content of this field is kept private and will not be shown publicly.'), ); $form['admin']['homepage'] = array( '#type' => 'textfield', '#title' => t('Homepage'), '#maxlength' => 255, '#size' => 30, '#default_value' => $edit['homepage'], ); } $form['admin']['date'] = array('#type' => 'textfield', '#parents' => array('date'), '#title' => t('Authored on'), '#size' => 20, '#maxlength' => 25, '#default_value' => $date, '#weight' => -1); $form['admin']['status'] = array('#type' => 'radios', '#parents' => array('status'), '#title' => t('Status'), '#default_value' => $status, '#options' => array(t('Published'), t('Not published')), '#weight' => -1); } else { $form['_author'] = array('#type' => 'item', '#title' => t('Your name'), '#value' => theme('username', $user) ); $form['author'] = array('#type' => 'value', '#value' => $user->name); } } else if (variable_get('comment_anonymous', COMMENT_ANONYMOUS_MAYNOT_CONTACT) == COMMENT_ANONYMOUS_MAY_CONTACT) { $form['name'] = array('#type' => 'textfield', '#title' => t('Your name'), '#maxlength' => 60, '#size' => 30, '#default_value' => $edit['name'] ? $edit['name'] : variable_get('anonymous', t('Anonymous')) ); $form['mail'] = array('#type' => 'textfield', '#title' => t('E-mail'), '#maxlength' => 64, '#size' => 30, '#default_value' => $edit['mail'], '#description' => t('The content of this field is kept private and will not be shown publicly.') ); $form['homepage'] = array('#type' => 'textfield', '#title' => t('Homepage'), '#maxlength' => 255, '#size' => 30, '#default_value' => $edit['homepage']); } else if (variable_get('comment_anonymous', COMMENT_ANONYMOUS_MAYNOT_CONTACT) == COMMENT_ANONYMOUS_MUST_CONTACT) { $form['name'] = array('#type' => 'textfield', '#title' => t('Your name'), '#maxlength' => 60, '#size' => 30, '#default_value' => $edit['name'] ? $edit['name'] : variable_get('anonymous', t('Anonymous')), '#required' => TRUE); $form['mail'] = array('#type' => 'textfield', '#title' => t('E-mail'), '#maxlength' => 64, '#size' => 30, '#default_value' => $edit['mail'], '#description' => t('The content of this field is kept private and will not be shown publicly.'), '#required' => TRUE); $form['homepage'] = array('#type' => 'textfield', '#title' => t('Homepage'), '#maxlength' => 255, '#size' => 30, '#default_value' => $edit['homepage']); } if (variable_get('comment_subject_field', 1) == 1) { $form['subject'] = array('#type' => 'textfield', '#title' => t('Subject'), '#maxlength' => 64, '#default_value' => $edit['subject']); } $form['comment_filter']['comment'] = array('#type' => 'textarea', '#title' => t('Comment'), '#rows' => 15, '#default_value' => $edit['comment'] ? $edit['comment'] : $user->signature, '#required' => TRUE); if (!isset($edit['format'])) { $edit['format'] = FILTER_FORMAT_DEFAULT; } $form['comment_filter']['format'] = filter_form($edit['format']); $form['cid'] = array('#type' => 'value', '#value' => $edit['cid']); $form['pid'] = array('#type' => 'value', '#value' => $edit['pid']); $form['nid'] = array('#type' => 'value', '#value' => $edit['nid']); $form['uid'] = array('#type' => 'value', '#value' => $edit['uid']); $form['preview'] = array('#type' => 'button', '#value' => t('Preview comment'), '#weight' => 19); $form['#token'] = 'comment'. $edit['nid'] . $edit['pid']; // Only show post button if preview is optional or if we are in preview mode. // We show the post button in preview mode even if there are form errors so that // optional form elements (e.g., captcha) can be updated in preview mode. if (!form_get_errors() && ((variable_get('comment_preview', COMMENT_PREVIEW_REQUIRED) == COMMENT_PREVIEW_OPTIONAL) || ($op == t('Preview comment')) || ($op == t('Post comment')))) { $form['submit'] = array('#type' => 'submit', '#value' => t('Post comment'), '#weight' => 20); } if ($op == t('Preview comment')) { $form['#after_build'] = array('comment_form_add_preview'); } if (empty($edit['cid']) && empty($edit['pid'])) { $form['#action'] = url('comment/reply/'. $edit['nid']); } // Graft in extra form additions $form = array_merge($form, comment_invoke_comment($form, 'form')); return $form; } function comment_form_box($edit, $title = NULL) { return theme('box', $title, drupal_get_form('comment_form', $edit, $title)); } function comment_form_add_preview($form, $edit) { global $user; drupal_set_title(t('Preview comment')); $output = ''; // Invoke full validation for the form, to protect against cross site // request forgeries (CSRF) and setting arbitrary values for fields such as // the input format. Preview the comment only when form validation does not // set any errors. drupal_validate_form($form['form_id']['#value'], $form); if (!form_get_errors()) { $comment = (object)_comment_form_submit($edit); // Attach the user and time information. if ($edit['author']) { $account = user_load(array('name' => $edit['author'])); } elseif ($user->uid && !isset($edit['is_anonymous'])) { $account = $user; } if ($account) { $comment->uid = $account->uid; $comment->name = check_plain($account->name); } $comment->timestamp = $edit['timestamp'] ? $edit['timestamp'] : time(); $output .= theme('comment_view', $comment); } $form['comment_preview'] = array( '#value' => $output, '#weight' => -100, '#prefix' => '
    ', '#suffix' => '
    ', ); $output = ''; if ($edit['pid']) { $comment = db_fetch_object(db_query('SELECT c.*, u.uid, u.name AS registered_name, u.picture, u.data FROM {comments} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.cid = %d AND c.status = %d', $edit['pid'], COMMENT_PUBLISHED)); $comment = drupal_unpack($comment); $comment->name = $comment->uid ? $comment->registered_name : $comment->name; $output .= theme('comment_view', $comment); } else { $suffix = empty($form['#suffix']) ? '' : $form['#suffix']; $form['#suffix'] = $suffix . node_view(node_load($edit['nid'])); $edit['pid'] = 0; } $form['comment_preview_below'] = array('#value' => $output, '#weight' => 100); return $form; } function comment_form_validate($form_id, $form_values) { comment_validate($form_values); } function _comment_form_submit($form_values) { if (!isset($form_values['date'])) { $form_values['date'] = 'now'; } $form_values['timestamp'] = strtotime($form_values['date']); if (isset($form_values['author'])) { $account = user_load(array('name' => $form_values['author'])); $form_values['uid'] = $account->uid; $form_values['name'] = $form_values['author']; } // Validate the comment's subject. If not specified, extract // one from the comment's body. if (trim($form_values['subject']) == '') { // The body may be in any format, so we: // 1) Filter it into HTML // 2) Strip out all HTML tags // 3) Convert entities back to plain-text. // Note: format is checked by check_markup(). $form_values['subject'] = trim(truncate_utf8(decode_entities(strip_tags(check_markup($form_values['comment'], $form_values['format']))), 29, TRUE)); // Edge cases where the comment body is populated only by HTML tags will // require a default subject. if ($form_values['subject'] == '') { $form_values['subject'] = t('(No subject)'); } } return $form_values; } function comment_form_submit($form_id, $form_values) { $form_values = _comment_form_submit($form_values); if ($cid = comment_save($form_values)) { return array('node/'. $form_values['nid'], NULL, "comment-$cid"); } } /* ** Renderer or visualization functions this can be optionally ** overridden by themes. */ function theme_comment_preview($comment, $links = array(), $visible = 1) { $output = '
    '; $output .= theme('comment_view', $comment, $links, $visible); $output .= '
    '; return $output; }; function theme_comment_view($comment, $links = array(), $visible = 1) { static $first_new = TRUE; $output = ''; $comment->new = node_mark($comment->nid, $comment->timestamp); if ($first_new && $comment->new != MARK_READ) { // Assign the anchor only for the first new comment. This avoids duplicate // id attributes on a page. $first_new = FALSE; $output .= "\n"; } $output .= "cid\">\n"; // Switch to folded/unfolded view of the comment if ($visible) { $comment->comment = check_markup($comment->comment, $comment->format, FALSE); // Comment API hook comment_invoke_comment($comment, 'view'); $output .= theme('comment', $comment, $links); } else { $output .= theme('comment_folded', $comment); } return $output; } function comment_controls($mode = COMMENT_MODE_THREADED_EXPANDED, $order = COMMENT_ORDER_NEWEST_FIRST, $comments_per_page = 50) { $form['mode'] = array('#type' => 'select', '#default_value' => $mode, '#options' => _comment_get_modes(), '#weight' => 1, ); $form['order'] = array( '#type' => 'select', '#default_value' => $order, '#options' => _comment_get_orders(), '#weight' => 2, ); foreach (_comment_per_page() as $i) { $options[$i] = t('!a comments per page', array('!a' => $i)); } $form['comments_per_page'] = array('#type' => 'select', '#default_value' => $comments_per_page, '#options' => $options, '#weight' => 3, ); $form['submit'] = array('#type' => 'submit', '#value' => t('Save settings'), '#weight' => 20, ); return $form; } function theme_comment_controls($form) { $output .= '
    '; $output .= drupal_render($form); $output .= '
    '; $output .= '
    '. t('Select your preferred way to display the comments and click "Save settings" to activate your changes.') .'
    '; return theme('box', t('Comment viewing options'), $output); } function comment_controls_submit($form_id, $form_values) { global $user; $mode = $form_values['mode']; $order = $form_values['order']; $comments_per_page = $form_values['comments_per_page']; if ($user->uid) { $user = user_save($user, array('mode' => $mode, 'sort' => $order, 'comments_per_page' => $comments_per_page)); } else { $_SESSION['comment_mode'] = $mode; $_SESSION['comment_sort'] = $order; $_SESSION['comment_comments_per_page'] = $comments_per_page; } } function theme_comment($comment, $links = array()) { $output = '
    '; $output .= '
    '. l($comment->subject, $_GET['q'], NULL, NULL, "comment-$comment->cid") .' '. theme('mark', $comment->new) ."
    \n"; $output .= '
    '. t('by %a on %b', array('%a' => theme('username', $comment), '%b' => format_date($comment->timestamp))) ."
    \n"; $output .= '
    '. $comment->comment .'
    '; $output .= ''; $output .= '
    '; return $output; } function theme_comment_folded($comment) { $output = "
    \n"; $output .= ' '. l($comment->subject, comment_node_url() .'/'. $comment->cid, NULL, NULL, "comment-$comment->cid") .' '. theme('mark', $comment->new) .' '; $output .= ''. t('by') .' '. theme('username', $comment) ."\n"; $output .= "
    \n"; return $output; } function theme_comment_flat_collapsed($comment) { return theme('comment_view', $comment, '', 0); } function theme_comment_flat_expanded($comment) { return theme('comment_view', $comment, module_invoke_all('link', 'comment', $comment, 0)); } function theme_comment_thread_collapsed($comment) { $output .= theme('comment_view', $comment, '', 0); return $output; } function theme_comment_thread_expanded($comment) { $output = ''; $output .= theme('comment_view', $comment, module_invoke_all('link', 'comment', $comment, 0)); return $output; } function theme_comment_post_forbidden($nid) { global $user; if ($user->uid) { return t("you can't post comments"); } else { // we cannot use drupal_get_destination() because these links sometimes appear on /node and taxo listing pages if (variable_get('comment_form_location', COMMENT_FORM_SEPARATE_PAGE) == COMMENT_FORM_SEPARATE_PAGE) { $destination = "destination=". drupal_urlencode("comment/reply/$nid#comment-form"); } else { $destination = "destination=". drupal_urlencode("node/$nid#comment-form"); } if (variable_get('user_register', 1)) { return t('Login or register to post comments', array('@login' => url('user/login', $destination), '@register' => url('user/register', $destination))); } else { return t('Login to post comments', array('@login' => url('user/login', $destination))); } } } /** * Allow themable wrapping of all comments. */ function theme_comment_wrapper($content) { return '
    '. $content .'
    '; } function _comment_delete_thread($comment) { if (!is_object($comment) || !is_numeric($comment->cid)) { watchdog('content', t('Can not delete non-existent comment.'), WATCHDOG_WARNING); return; } // Delete the comment: db_query('DELETE FROM {comments} WHERE cid = %d', $comment->cid); watchdog('content', t('Comment: deleted %subject.', array('%subject' => $comment->subject))); comment_invoke_comment($comment, 'delete'); // Delete the comment's replies $result = db_query('SELECT c.*, u.name AS registered_name, u.uid FROM {comments} c INNER JOIN {users} u ON u.uid = c.uid WHERE pid = %d', $comment->cid); while ($comment = db_fetch_object($result)) { $comment->name = $comment->uid ? $comment->registered_name : $comment->name; _comment_delete_thread($comment); } } /** * Return an array of viewing modes for comment listings. * * We can't use a global variable array because the locale system * is not initialized yet when the comment module is loaded. */ function _comment_get_modes() { return array( COMMENT_MODE_FLAT_COLLAPSED => t('Flat list - collapsed'), COMMENT_MODE_FLAT_EXPANDED => t('Flat list - expanded'), COMMENT_MODE_THREADED_COLLAPSED => t('Threaded list - collapsed'), COMMENT_MODE_THREADED_EXPANDED => t('Threaded list - expanded') ); } /** * Return an array of viewing orders for comment listings. * * We can't use a global variable array because the locale system * is not initialized yet when the comment module is loaded. */ function _comment_get_orders() { return array( COMMENT_ORDER_NEWEST_FIRST => t('Date - newest first'), COMMENT_ORDER_OLDEST_FIRST => t('Date - oldest first') ); } /** * Return an array of "comments per page" settings from which the user * can choose. */ function _comment_per_page() { return drupal_map_assoc(array(10, 30, 50, 70, 90, 150, 200, 250, 300)); } /** * Return a current comment display setting * * $setting can be one of these: 'mode', 'sort', 'comments_per_page' */ function _comment_get_display_setting($setting) { global $user; if (isset($_GET[$setting])) { $value = $_GET[$setting]; } else { // get the setting's site default switch ($setting) { case 'mode': $default = variable_get('comment_default_mode', COMMENT_MODE_THREADED_EXPANDED); break; case 'sort': $default = variable_get('comment_default_order', COMMENT_ORDER_NEWEST_FIRST); break; case 'comments_per_page': $default = variable_get('comment_default_per_page', '50'); } if (variable_get('comment_controls', COMMENT_CONTROLS_HIDDEN) == COMMENT_CONTROLS_HIDDEN) { // if comment controls are disabled use site default $value = $default; } else { // otherwise use the user's setting if set if ($user->$setting) { $value = $user->$setting; } else if ($_SESSION['comment_'. $setting]) { $value = $_SESSION['comment_'. $setting]; } else { $value = $default; } } } return $value; } /** * Updates the comment statistics for a given node. This should be called any * time a comment is added, deleted, or updated. * * The following fields are contained in the node_comment_statistics table. * - last_comment_timestamp: the timestamp of the last comment for this node or the node create stamp if no comments exist for the node. * - last_comment_name: the name of the anonymous poster for the last comment * - last_comment_uid: the uid of the poster for the last comment for this node or the node authors uid if no comments exists for the node. * - comment_count: the total number of approved/published comments on this node. */ function _comment_update_node_statistics($nid) { $count = db_result(db_query('SELECT COUNT(cid) FROM {comments} WHERE nid = %d AND status = %d', $nid, COMMENT_PUBLISHED)); // comments exist if ($count > 0) { $last_reply = db_fetch_object(db_query_range('SELECT cid, name, timestamp, uid FROM {comments} WHERE nid = %d AND status = %d ORDER BY cid DESC', $nid, COMMENT_PUBLISHED, 0, 1)); db_query("UPDATE {node_comment_statistics} SET comment_count = %d, last_comment_timestamp = %d, last_comment_name = '%s', last_comment_uid = %d WHERE nid = %d", $count, $last_reply->timestamp, $last_reply->uid ? '' : $last_reply->name, $last_reply->uid, $nid); } // no comments else { $node = db_fetch_object(db_query("SELECT uid, created FROM {node} WHERE nid = %d", $nid)); db_query("UPDATE {node_comment_statistics} SET comment_count = 0, last_comment_timestamp = %d, last_comment_name = '', last_comment_uid = %d WHERE nid = %d", $node->created, $node->uid, $nid); } } /** * Invoke a hook_comment() operation in all modules. * * @param &$comment * A comment object. * @param $op * A string containing the name of the comment operation. * @return * The returned value of the invoked hooks. */ function comment_invoke_comment(&$comment, $op) { $return = array(); foreach (module_implements('comment') as $name) { $function = $name .'_comment'; $result = $function($comment, $op); if (isset($result) && is_array($result)) { $return = array_merge($return, $result); } else if (isset($result)) { $return[] = $result; } } return $return; } /** * Generate vancode. * * Consists of a leading character indicating length, followed by N digits * with a numerical value in base 36. Vancodes can be sorted as strings * without messing up numerical order. * * It goes: * 00, 01, 02, ..., 0y, 0z, * 110, 111, ... , 1zy, 1zz, * 2100, 2101, ..., 2zzy, 2zzz, * 31000, 31001, ... */ function int2vancode($i = 0) { $num = base_convert((int)$i, 10, 36); $length = strlen($num); return chr($length + ord('0') - 1) . $num; } /** * Decode vancode back to an integer. */ function vancode2int($c = '00') { return base_convert(substr($c, 1), 36, 10); } loki_website/modules/comment/comment.install0000644000004100000410000000211510636151716021737 0ustar www-datawww-datachanged to avoid future * timestamps. */ function comment_update_1() { // Change any future last comment timestamps to now. db_query('UPDATE {node_comment_statistics} SET last_comment_timestamp = %d WHERE last_comment_timestamp > %d', time(), time()); // Unstuck node indexing timestamp if needed. if (($last = variable_get('node_cron_last', FALSE)) !== FALSE) { variable_set('node_cron_last', min(time(), $last)); } return array(); } loki_website/modules/filter/0000755000004100000410000000000010744012100016510 5ustar www-datawww-dataloki_website/modules/filter/filter.module0000644000004100000410000016353310741514300021225 0ustar www-datawww-data'. t("The filter module allows administrators to configure text input formats for the site. For example, an administrator may want a filter to strip out malicious HTML from user's comments. Administrators may also want to make URLs linkable even if they are only entered in an unlinked format.") .'

    '; $output .= '

    '. t('Users can choose between the available input formats when creating or editing content. Administrators can configure which input formats are available to which user roles, as well as choose a default input format. Administrators can also create new input formats. Each input format can be configured to use a selection of filters.') .'

    '; $output .= '

    '. t('For more information please read the configuration and customization handbook Filter page.', array('@filter' => 'http://drupal.org/handbook/modules/filter/')) .'

    '; return $output; case 'admin/settings/filters': return t('

    Input formats define a way of processing user-supplied text in Drupal. Every input format has its own settings of which filters to apply. Possible filters include stripping out malicious HTML and making URLs clickable.

    Users can choose between the available input formats when submitting content.

    Below you can configure which input formats are available to which roles, as well as choose a default input format (used for imported content, for example).

    Note that (1) the default format is always available to all roles, and (2) all filter formats can always be used by roles with the "administer filters" permission even if they are not explicitly listed in the Roles column of this table.

    '); case 'admin/settings/filters/'. arg(3): return t('

    Every filter performs one particular change on the user input, for example stripping out malicious HTML or making URLs clickable. Choose which filters you want to apply to text in this input format.

    If you notice some filters are causing conflicts in the output, you can rearrange them.

    ', array('@rearrange' => url('admin/settings/filters/'. arg(3) .'/order'))); case 'admin/settings/filters/'. arg(3) .'/configure': return '

    '. t('If you cannot find the settings for a certain filter, make sure you have enabled it on the view tab first.', array('@url' => url('admin/settings/filters/'. arg(3)))) .'

    '; case 'admin/settings/filters/'. arg(3) .'/order': return t('

    Because of the flexible filtering system, you might encounter a situation where one filter prevents another from doing its job. For example: a word in an URL gets converted into a glossary term, before the URL can be converted in a clickable link. When this happens, you will need to rearrange the order in which filters get executed.

    Filters are executed from top-to-bottom. You can use the weight column to rearrange them: heavier filters "sink" to the bottom.

    '); } } /** * Implementation of hook_menu(). */ function filter_menu($may_cache) { $items = array(); if ($may_cache) { $items[] = array('path' => 'admin/settings/filters', 'title' => t('Input formats'), 'description' => t('Configure how content input by users is filtered, including allowed HTML tags, PHP code tags. Also allows enabling of module-provided filters.'), 'callback' => 'drupal_get_form', 'callback arguments' => array('filter_admin_overview'), 'access' => user_access('administer filters'), ); $items[] = array('path' => 'admin/settings/filters/list', 'title' => t('List'), 'callback' => 'filter_admin_overview', 'type' => MENU_DEFAULT_LOCAL_TASK, 'access' => user_access('administer filters'), ); $items[] = array('path' => 'admin/settings/filters/add', 'title' => t('Add input format'), 'callback' => 'drupal_get_form', 'callback arguments' => array('filter_admin_format_form'), 'type' => MENU_LOCAL_TASK, 'weight' => 1, 'access' => user_access('administer filters'), ); $items[] = array('path' => 'admin/settings/filters/delete', 'title' => t('Delete input format'), 'callback' => 'drupal_get_form', 'callback arguments' => array('filter_admin_delete'), 'type' => MENU_CALLBACK, 'access' => user_access('administer filters'), ); $items[] = array('path' => 'filter/tips', 'title' => t('Compose tips'), 'callback' => 'filter_tips_long', 'access' => TRUE, 'type' => MENU_SUGGESTED_ITEM, ); } else { if (arg(0) == 'admin' && arg(1) == 'settings' && arg(2) == 'filters' && is_numeric(arg(3))) { $formats = filter_formats(); if (isset($formats[arg(3)])) { $items[] = array('path' => 'admin/settings/filters/'. arg(3), 'title' => t("!format input format", array('!format' => $formats[arg(3)]->name)), 'callback' => 'drupal_get_form', 'callback arguments' => array('filter_admin_format_form', $formats[arg(3)]), 'type' => MENU_CALLBACK, 'access' => user_access('administer filters'), ); $items[] = array('path' => 'admin/settings/filters/'. arg(3) .'/list', 'title' => t('View'), 'callback' => 'drupal_get_form', 'callback arguments' => array('filter_admin_format_form', $formats[arg(3)]), 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => 0, 'access' => user_access('administer filters'), ); $items[] = array('path' => 'admin/settings/filters/'. arg(3) .'/configure', 'title' => t('Configure'), 'callback' => 'drupal_get_form', 'callback arguments' => array('filter_admin_configure'), 'type' => MENU_LOCAL_TASK, 'weight' => 1, 'access' => user_access('administer filters'), ); $items[] = array('path' => 'admin/settings/filters/'. arg(3) .'/order', 'title' => t('Rearrange'), 'callback' => 'drupal_get_form', 'callback arguments' => array('filter_admin_order', 'format' => $formats[arg(3)]), 'type' => MENU_LOCAL_TASK, 'weight' => 2, 'access' => user_access('administer filters'), ); } } } return $items; } /** * Implementation of hook_perm(). */ function filter_perm() { return array('administer filters'); } /** * Implementation of hook_cron(). * * Expire outdated filter cache entries */ function filter_cron() { cache_clear_all(NULL, 'cache_filter'); } /** * Implementation of hook_filter_tips(). */ function filter_filter_tips($delta, $format, $long = FALSE) { global $base_url; switch ($delta) { case 0: if (variable_get("filter_html_$format", FILTER_HTML_STRIP) == FILTER_HTML_STRIP) { if ($allowed_html = variable_get("allowed_html_$format", '
      1. ')) { switch ($long) { case 0: return t('Allowed HTML tags: @tags', array('@tags' => $allowed_html)); case 1: $output = '

        '. t('Allowed HTML tags: @tags', array('@tags' => $allowed_html)) .'

        '; if (!variable_get("filter_html_help_$format", 1)) { return $output; } $output .= t('

        This site allows HTML content. While learning all of HTML may feel intimidating, learning how to use a very small number of the most basic HTML "tags" is very easy. This table provides examples for each tag that is enabled on this site.

        For more information see W3C\'s HTML Specifications or use your favorite search engine to find other sites that explain HTML.

        '); $tips = array( 'a' => array( t('Anchors are used to make links to other pages.'), ''. variable_get('site_name', 'Drupal') .''), 'br' => array( t('By default line break tags are automatically added, so use this tag to add additional ones. Use of this tag is different because it is not used with an open/close pair like all the others. Use the extra " /" inside the tag to maintain XHTML 1.0 compatibility'), t('Text with
        line break')), 'p' => array( t('By default paragraph tags are automatically added, so use this tag to add additional ones.'), '

        '. t('Paragraph one.') .'

        '. t('Paragraph two.') .'

        '), 'strong' => array( t('Strong'), ''. t('Strong'). ''), 'em' => array( t('Emphasized'), ''. t('Emphasized') .''), 'cite' => array( t('Cited'), ''. t('Cited') .''), 'code' => array( t('Coded text used to show programming source code'), ''. t('Coded') .''), 'b' => array( t('Bolded'), ''. t('Bolded') .''), 'u' => array( t('Underlined'), ''. t('Underlined') .''), 'i' => array( t('Italicized'), ''. t('Italicized') .''), 'sup' => array( t('Superscripted'), t('Superscripted')), 'sub' => array( t('Subscripted'), t('Subscripted')), 'pre' => array( t('Preformatted'), '
        '. t('Preformatted') .'
        '), 'abbr' => array( t('Abbreviation'), t('Abbrev.')), 'acronym' => array( t('Acronym'), t('TLA')), 'blockquote' => array( t('Block quoted'), '
        '. t('Block quoted') .'
        '), 'q' => array( t('Quoted inline'), ''. t('Quoted inline') .''), // Assumes and describes tr, td, th. 'table' => array( t('Table'), '
        '. t('Table header') .'
        '. t('Table cell') .'
        '), 'tr' => NULL, 'td' => NULL, 'th' => NULL, 'del' => array( t('Deleted'), ''. t('Deleted') .''), 'ins' => array( t('Inserted'), ''. t('Inserted') .''), // Assumes and describes li. 'ol' => array( t('Ordered list - use the <li> to begin each list item'), '
        1. '. t('First item') .'
        2. '. t('Second item') .'
        '), 'ul' => array( t('Unordered list - use the <li> to begin each list item'), '
        • '. t('First item') .'
        • '. t('Second item') .'
        '), 'li' => NULL, // Assumes and describes dt and dd. 'dl' => array( t('Definition lists are similar to other HTML lists. <dl> begins the definition list, <dt> begins the definition term and <dd> begins the definition description.'), '
        '. t('First term') .'
        '. t('First definition') .'
        '. t('Second term') .'
        '. t('Second definition') .'
        '), 'dt' => NULL, 'dd' => NULL, 'h1' => array( t('Header'), '

        '. t('Title') .'

        '), 'h2' => array( t('Header'), '

        '. t('Subtitle') .'

        '), 'h3' => array( t('Header'), '

        '. t('Subtitle three') .'

        '), 'h4' => array( t('Header'), '

        '. t('Subtitle four') .'

        '), 'h5' => array( t('Header'), '
        '. t('Subtitle five') .'
        '), 'h6' => array( t('Header'), '
        '. t('Subtitle six') .'
        ') ); $header = array(t('Tag Description'), t('You Type'), t('You Get')); preg_match_all('/<([a-z0-9]+)[^a-z0-9]/i', $allowed_html, $out); foreach ($out[1] as $tag) { if (array_key_exists($tag, $tips)) { if ($tips[$tag]) { $rows[] = array( array('data' => $tips[$tag][0], 'class' => 'description'), array('data' => ''. check_plain($tips[$tag][1]) .'', 'class' => 'type'), array('data' => $tips[$tag][1], 'class' => 'get') ); } } else { $rows[] = array( array('data' => t('No help provided for tag %tag.', array('%tag' => $tag)), 'class' => 'description', 'colspan' => 3), ); } } $output .= theme('table', $header, $rows); $output .= t('

        Most unusual characters can be directly entered without any problems.

        If you do encounter problems, try using HTML character entities. A common example looks like &amp; for an ampersand & character. For a full list of entities see HTML\'s entities page. Some of the available characters include:

        '); $entities = array( array( t('Ampersand'), '&'), array( t('Greater than'), '>'), array( t('Less than'), '<'), array( t('Quotation mark'), '"'), ); $header = array(t('Character Description'), t('You Type'), t('You Get')); unset($rows); foreach ($entities as $entity) { $rows[] = array( array('data' => $entity[0], 'class' => 'description'), array('data' => ''. check_plain($entity[1]) .'', 'class' => 'type'), array('data' => $entity[1], 'class' => 'get') ); } $output .= theme('table', $header, $rows); return $output; } } else { return t('No HTML tags allowed'); } } break; case 1: switch ($long) { case 0: return t('You may post PHP code. You should include <?php ?> tags.'); case 1: return t('

        Using custom PHP code

        If you know how to script in PHP, Drupal gives you the power to embed any script you like. It will be executed when the page is viewed and dynamically embedded into the page. This gives you amazing flexibility and power, but of course with that comes danger and insecurity if you do not write good code. If you are not familiar with PHP, SQL or with the site engine, avoid experimenting with PHP because you can corrupt your database or render your site insecure or even unusable! If you do not plan to do fancy stuff with your content then you are probably better off with straight HTML.

        Remember that the code within each PHP item must be valid PHP code - including things like correctly terminating statements with a semicolon. It is highly recommended that you develop your code separately using a simple test script on top of a test database before migrating to your production environment.

        Notes:

        • You can use global variables, such as configuration parameters, within the scope of your PHP code but remember that global variables which have been given values in your code will retain these values in the engine afterwards.
        • register_globals is now set to off by default. If you need form information you need to get it from the "superglobals" $_POST, $_GET, etc.
        • You can either use the print or return statement to output the actual content for your item.

        A basic example:

        You want to have a box with the title "Welcome" that you use to greet your visitors. The content for this box could be created by going:

          print t("Welcome visitor, ... welcome message goes here ...");
        

        If we are however dealing with a registered user, we can customize the message by using:

          global $user;
          if ($user->uid) {
            print t("Welcome $user->name, ... welcome message goes here ...");
          }
          else {
            print t("Welcome visitor, ... welcome message goes here ...");
          }
        

        For more in-depth examples, we recommend that you check the existing Drupal code and use it as a starting point, especially for sidebar boxes.

        '); } case 2: switch ($long) { case 0: return t('Lines and paragraphs break automatically.'); case 1: return t('Lines and paragraphs are automatically recognized. The <br /> line break, <p> paragraph and </p> close paragraph tags are inserted automatically. If paragraphs are not recognized simply add a couple blank lines.'); } case 3: return t('Web page addresses and e-mail addresses turn into links automatically.'); } } /** * Displays a list of all input formats and which one is the default */ function filter_admin_overview() { // Overview of all formats. $formats = filter_formats(); $error = FALSE; foreach ($formats as $id => $format) { $roles = array(); foreach (user_roles() as $rid => $name) { // Prepare a roles array with roles that may access the filter if (strstr($format->roles, ",$rid,")) { $roles[] = $name; } } $default = ($id == variable_get('filter_default_format', 1)); $options[$id] = ''; $form[$format->name]['id'] = array('#value' => $id); $form[$format->name]['roles'] = array('#value' => $default ? t('All roles may use default format') : ($roles ? implode(', ',$roles) : t('No roles may use this format'))); $form[$format->name]['configure'] = array('#value' => l(t('configure'), 'admin/settings/filters/'. $id)); $form[$format->name]['delete'] = array('#value' => $default ? '' : l(t('delete'), 'admin/settings/filters/delete/'. $id)); } $form['default'] = array('#type' => 'radios', '#options' => $options, '#default_value' => variable_get('filter_default_format', 1)); $form['submit'] = array('#type' => 'submit', '#value' => t('Set default format')); return $form; } function filter_admin_overview_submit($form_id, $form_values) { // Process form submission to set the default format if (is_numeric($form_values['default'])) { drupal_set_message(t('Default format updated.')); variable_set('filter_default_format', $form_values['default']); } } function theme_filter_admin_overview($form) { $rows = array(); foreach ($form as $name => $element) { if (isset($element['roles']) && is_array($element['roles'])) { $rows[] = array( drupal_render($form['default'][$element['id']['#value']]), check_plain($name), drupal_render($element['roles']), drupal_render($element['configure']), drupal_render($element['delete']) ); unset($form[$name]); } } $header = array(t('Default'), t('Name'), t('Roles'), array('data' => t('Operations'), 'colspan' => 2)); $output = theme('table', $header, $rows); $output .= drupal_render($form); return $output; } /** * Menu callback; confirm deletion of a format. */ function filter_admin_delete() { $format = arg(4); $format = db_fetch_object(db_query('SELECT * FROM {filter_formats} WHERE format = %d', $format)); if ($format) { if ($format->format != variable_get('filter_default_format', 1)) { $form['format'] = array('#type' => 'hidden', '#value' => $format->format); $form['name'] = array('#type' => 'hidden', '#value' => $format->name); return confirm_form($form, t('Are you sure you want to delete the input format %format?', array('%format' => $format->name)), 'admin/settings/filters', t('If you have any content left in this input format, it will be switched to the default input format. This action cannot be undone.'), t('Delete'), t('Cancel')); } else { drupal_set_message(t('The default format cannot be deleted.')); drupal_goto('admin/settings/filters'); } } else { drupal_not_found(); } } /** * Process filter delete form submission. */ function filter_admin_delete_submit($form_id, $form_values) { db_query("DELETE FROM {filter_formats} WHERE format = %d", $form_values['format']); db_query("DELETE FROM {filters} WHERE format = %d", $form_values['format']); $default = variable_get('filter_default_format', 1); // Replace existing instances of the deleted format with the default format. db_query("UPDATE {node_revisions} SET format = %d WHERE format = %d", $default, $form_values['format']); db_query("UPDATE {comments} SET format = %d WHERE format = %d", $default, $form_values['format']); db_query("UPDATE {boxes} SET format = %d WHERE format = %d", $default, $form_values['format']); cache_clear_all($form_values['format'] .':', 'cache_filter', TRUE); drupal_set_message(t('Deleted input format %format.', array('%format' => $form_values['name']))); return 'admin/settings/filters'; } /** * Generate a filter format form. */ function filter_admin_format_form($format = NULL) { $default = ($format->format == variable_get('filter_default_format', 1)); if ($default) { $help = t('All roles for the default format must be enabled and cannot be changed.'); $form['default_format'] = array('#type' => 'hidden', '#value' => 1); } $form['name'] = array('#type' => 'textfield', '#title' => 'Name', '#default_value' => $format->name, '#description' => t('Specify a unique name for this filter format.'), '#required' => TRUE, ); // Add a row of checkboxes for form group. $form['roles'] = array('#type' => 'fieldset', '#title' => t('Roles'), '#description' => $default ? $help : t('Choose which roles may use this filter format. Note that roles with the "administer filters" permission can always use all the filter formats.'), '#tree' => TRUE, ); foreach (user_roles() as $rid => $name) { $checked = strstr($format->roles, ",$rid,"); $form['roles'][$rid] = array('#type' => 'checkbox', '#title' => $name, '#default_value' => ($default || $checked), ); if ($default) { $form['roles'][$rid]['#disabled'] = TRUE; } } // Table with filters $all = filter_list_all(); $enabled = filter_list_format($format->format); $form['filters'] = array('#type' => 'fieldset', '#title' => t('Filters'), '#description' => t('Choose the filters that will be used in this filter format.'), '#tree' => TRUE, ); foreach ($all as $id => $filter) { $form['filters'][$id] = array('#type' => 'checkbox', '#title' => $filter->name, '#default_value' => isset($enabled[$id]), '#description' => module_invoke($filter->module, 'filter', 'description', $filter->delta), ); } if (isset($format)) { $form['format'] = array('#type' => 'hidden', '#value' => $format->format); // Composition tips (guidelines) $tips = _filter_tips($format->format, FALSE); $extra = '

        '. l(t('More information about formatting options'), 'filter/tips') .'

        '; $tiplist = theme('filter_tips', $tips, FALSE, $extra); if (!$tiplist) { $tiplist = '

        '. t('No guidelines available.') .'

        '; } $group = '

        '. t('These are the guidelines that users will see for posting in this input format. They are automatically generated from the filter settings.') .'

        '; $group .= $tiplist; $form['tips'] = array('#value' => '

        '. t('Formatting guidelines') .'

        '. $group); } $form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration')); return $form; } /** * Validate filter format form submissions. */ function filter_admin_format_form_validate($form_id, $form_values) { if (!isset($form_values['format'])) { $name = trim($form_values['name']); $result = db_fetch_object(db_query("SELECT format FROM {filter_formats} WHERE name='%s'", $name)); if ($result) { form_set_error('name', t('Filter format names need to be unique. A format named %name already exists.', array('%name' => $name))); } } } /** * Process filter format form submissions. */ function filter_admin_format_form_submit($form_id, $form_values) { $format = isset($form_values['format']) ? $form_values['format'] : NULL; $current = filter_list_format($format); $name = trim($form_values['name']); $cache = TRUE; // Add a new filter format. if (!$format) { $new = TRUE; db_query("INSERT INTO {filter_formats} (name) VALUES ('%s')", $name); $format = db_result(db_query("SELECT MAX(format) AS format FROM {filter_formats}")); drupal_set_message(t('Added input format %format.', array('%format' => $name))); } else { drupal_set_message(t('The input format settings have been updated.')); } db_query("DELETE FROM {filters} WHERE format = %d", $format); foreach ($form_values['filters'] as $id => $checked) { if ($checked) { list($module, $delta) = explode('/', $id); // Add new filters to the bottom. $weight = isset($current[$id]->weight) ? $current[$id]->weight : 10; db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (%d, '%s', %d, %d)", $format, $module, $delta, $weight); // Check if there are any 'no cache' filters. $cache &= !module_invoke($module, 'filter', 'no cache', $delta); } } // We store the roles as a string for ease of use. // We should always set all roles to TRUE when saving a default role. // We use leading and trailing comma's to allow easy substring matching. $roles = array(); if (isset($form_values['roles'])) { foreach ($form_values['roles'] as $id => $checked) { if ($checked) { $roles[] = $id; } } } $roles = ','. implode(',', ($form_values['default_format'] ? array_keys(user_roles()) : $roles)) .','; db_query("UPDATE {filter_formats} SET cache = %d, name='%s', roles = '%s' WHERE format = %d", $cache, $name, $roles, $format); cache_clear_all($format .':', 'cache_filter', TRUE); // If a new filter was added, return to the main list of filters. Otherwise, stay on edit filter page to show new changes. if ($new) { return 'admin/settings/filters/'; } else { return 'admin/settings/filters/'. $format; } } /** * Menu callback; display form for ordering filters for a format. */ function filter_admin_order($format = NULL) { // Get list (with forced refresh) $filters = filter_list_format($format->format); $form['weights'] = array('#tree' => TRUE); foreach ($filters as $id => $filter) { $form['names'][$id] = array('#value' => $filter->name); $form['weights'][$id] = array('#type' => 'weight', '#default_value' => $filter->weight); } $form['format'] = array('#type' => 'hidden', '#value' => $format->format); $form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration')); return $form; } /** * Theme filter order configuration form. */ function theme_filter_admin_order($form) { $header = array(t('Name'), t('Weight')); $rows = array(); foreach (element_children($form['names']) as $id) { // Don't take form control structures if (is_array($form['names'][$id])) { $rows[] = array(drupal_render($form['names'][$id]), drupal_render($form['weights'][$id])); } } $output = theme('table', $header, $rows); $output .= drupal_render($form); return $output; } /** * Process filter order configuration form submission. */ function filter_admin_order_submit($form_id, $form_values) { foreach ($form_values['weights'] as $id => $weight) { list($module, $delta) = explode('/', $id); db_query("UPDATE {filters} SET weight = %d WHERE format = %d AND module = '%s' AND delta = %d", $weight, $form_values['format'], $module, $delta); } drupal_set_message(t('The filter ordering has been saved.')); cache_clear_all($form_values['format'] .':', 'cache_filter', TRUE); } /** * Menu callback; display settings defined by filters. */ function filter_admin_configure() { $format = arg(3); $list = filter_list_format($format); $form = array(); foreach ($list as $filter) { $form_module = module_invoke($filter->module, 'filter', 'settings', $filter->delta, $format); if (isset($form_module) && is_array($form_module)) { $form = array_merge($form, $form_module); } } if (!empty($form)) { $form = system_settings_form($form); $form['format'] = array('#type' => 'hidden', '#value' => $format->format); $form['#submit'][] = 'filter_admin_configure_submit'; } else { $form['error'] = array('#value' => t('No settings are available.')); } return $form; } /** * Clear the filter's cache when configuration settings are saved. */ function filter_admin_configure_submit($form_id, $form_values) { cache_clear_all($form_values['format'] .':', 'cache_filter', TRUE); } /** * Retrieve a list of input formats. */ function filter_formats() { global $user; static $formats; // Administrators can always use all input formats. $all = user_access('administer filters'); if (!isset($formats)) { $formats = array(); $query = 'SELECT * FROM {filter_formats}'; // Build query for selecting the format(s) based on the user's roles. $args = array(); if (!$all) { $where = array(); foreach ($user->roles as $rid => $role) { $where[] = "roles LIKE '%%,%d,%%'"; $args[] = $rid; } $query .= ' WHERE '. implode(' OR ', $where) . ' OR format = %d'; $args[] = variable_get('filter_default_format', 1); } $result = db_query($query, $args); while ($format = db_fetch_object($result)) { $formats[$format->format] = $format; } } return $formats; } /** * Build a list of all filters. */ function filter_list_all() { $filters = array(); foreach (module_list() as $module) { $list = module_invoke($module, 'filter', 'list'); if (isset($list) && is_array($list)) { foreach ($list as $delta => $name) { $filters[$module .'/'. $delta] = (object)array('module' => $module, 'delta' => $delta, 'name' => $name); } } } uasort($filters, '_filter_list_cmp'); return $filters; } /** * Helper function for sorting the filter list by filter name. */ function _filter_list_cmp($a, $b) { return strcmp($a->name, $b->name); } /** * Resolve a format id, including the default format. */ function filter_resolve_format($format) { return $format == FILTER_FORMAT_DEFAULT ? variable_get('filter_default_format', 1) : $format; } /** * Check if text in a certain input format is allowed to be cached. */ function filter_format_allowcache($format) { static $cache = array(); $format = filter_resolve_format($format); if (!isset($cache[$format])) { $cache[$format] = db_result(db_query('SELECT cache FROM {filter_formats} WHERE format = %d', $format)); } return $cache[$format]; } /** * Retrieve a list of filters for a certain format. */ function filter_list_format($format) { static $filters = array(); if (!isset($filters[$format])) { $filters[$format] = array(); $result = db_query("SELECT * FROM {filters} WHERE format = %d ORDER BY weight ASC", $format); while ($filter = db_fetch_object($result)) { $list = module_invoke($filter->module, 'filter', 'list'); if (isset($list) && is_array($list) && isset($list[$filter->delta])) { $filter->name = $list[$filter->delta]; $filters[$format][$filter->module .'/'. $filter->delta] = $filter; } } } return $filters[$format]; } /** * @name Filtering functions * @{ * Modules which need to have content filtered can use these functions to * interact with the filter system. * * For more info, see the hook_filter() documentation. * * Note: because filters can inject JavaScript or execute PHP code, security is * vital here. When a user supplies a $format, you should validate it with * filter_access($format) before accepting/using it. This is normally done in * the validation stage of the node system. You should for example never make a * preview of content in a disallowed format. */ /** * Run all the enabled filters on a piece of text. * * @param $text * The text to be filtered. * @param $format * The format of the text to be filtered. Specify FILTER_FORMAT_DEFAULT for * the default format. * @param $check * Whether to check the $format with filter_access() first. Defaults to TRUE. * Note that this will check the permissions of the current user, so you * should specify $check = FALSE when viewing other people's content. When * showing content that is not (yet) stored in the database (eg. upon preview), * set to TRUE so the user's permissions are checked. */ function check_markup($text, $format = FILTER_FORMAT_DEFAULT, $check = TRUE) { // When $check = TRUE, do an access check on $format. if (isset($text) && (!$check || filter_access($format))) { $format = filter_resolve_format($format); // Check for a cached version of this piece of text. $id = $format .':'. md5($text); if ($cached = cache_get($id, 'cache_filter')) { return $cached->data; } // See if caching is allowed for this format. $cache = filter_format_allowcache($format); // Convert all Windows and Mac newlines to a single newline, // so filters only need to deal with one possibility. $text = str_replace(array("\r\n", "\r"), "\n", $text); // Get a complete list of filters, ordered properly. $filters = filter_list_format($format); // Give filters the chance to escape HTML-like data such as code or formulas. foreach ($filters as $filter) { $text = module_invoke($filter->module, 'filter', 'prepare', $filter->delta, $format, $text); } // Perform filtering. foreach ($filters as $filter) { $text = module_invoke($filter->module, 'filter', 'process', $filter->delta, $format, $text); } // Store in cache with a minimum expiration time of 1 day. if ($cache) { cache_set($id, 'cache_filter', $text, time() + (60 * 60 * 24)); } } else { $text = t('n/a'); } return $text; } /** * Generate a selector for choosing a format in a form. * * @param $value * The ID of the format that is currently selected. * @param $weight * The weight of the input format. * @param $parents * Required when defining multiple input formats on a single node or having a different parent than 'format'. * @return * HTML for the form element. */ function filter_form($value = FILTER_FORMAT_DEFAULT, $weight = NULL, $parents = array('format')) { $value = filter_resolve_format($value); $formats = filter_formats(); $extra = theme('filter_tips_more_info'); if (count($formats) > 1) { $form = array( '#type' => 'fieldset', '#title' => t('Input format'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#weight' => $weight, '#validate' => array('filter_form_validate' => array()), ); // Multiple formats available: display radio buttons with tips. foreach ($formats as $format) { $form[$format->format] = array( '#type' => 'radio', '#title' => $format->name, '#default_value' => $value, '#return_value' => $format->format, '#parents' => $parents, '#description' => theme('filter_tips', _filter_tips($format->format, FALSE)), ); } } else { // Only one format available: use a hidden form item and only show tips. $format = array_shift($formats); $form[$format->format] = array('#type' => 'value', '#value' => $format->format, '#parents' => $parents); $tips = _filter_tips(variable_get('filter_default_format', 1), FALSE); $form['format']['guidelines'] = array( '#title' => t('Formatting guidelines'), '#value' => theme('filter_tips', $tips, FALSE, $extra), ); } $form[] = array('#value' => $extra); return $form; } function filter_form_validate($form) { foreach (element_children($form) as $key) { if ($form[$key]['#value'] == $form[$key]['#return_value']) { return; } } form_error($form, t('An illegal choice has been detected. Please contact the site administrator.')); watchdog('form', t('Illegal choice %choice in %name element.', array('%choice' => $form[$key]['#value'], '%name' => empty($form['#title']) ? $form['#parents'][0] : $form['#title'])), WATCHDOG_ERROR); } /** * Returns TRUE if the user is allowed to access this format. */ function filter_access($format) { $format = filter_resolve_format($format); if (user_access('administer filters') || ($format == variable_get('filter_default_format', 1))) { return TRUE; } else { $formats = filter_formats(); return isset($formats[$format]); } } /** * @} End of "Filtering functions". */ /** * Menu callback; show a page with long filter tips. */ function filter_tips_long() { $format = arg(2); if ($format) { $output = theme('filter_tips', _filter_tips($format, TRUE), TRUE); } else { $output = theme('filter_tips', _filter_tips(-1, TRUE), TRUE); } return $output; } /** * Helper function for fetching filter tips. */ function _filter_tips($format, $long = FALSE) { if ($format == -1) { $formats = filter_formats(); } else { $formats = array(db_fetch_object(db_query("SELECT * FROM {filter_formats} WHERE format = %d", $format))); } $tips = array(); foreach ($formats as $format) { $filters = filter_list_format($format->format); $tips[$format->name] = array(); foreach ($filters as $id => $filter) { if ($tip = module_invoke($filter->module, 'filter_tips', $filter->delta, $format->format, $long)) { $tips[$format->name][] = array('tip' => $tip, 'id' => $id); } } } return $tips; } /** * Format a set of filter tips. * * @ingroup themeable */ function theme_filter_tips($tips, $long = FALSE, $extra = '') { $output = ''; $multiple = count($tips) > 1; if ($multiple) { $output = t('input formats') .':'; } if (count($tips)) { if ($multiple) { $output .= '
          '; } foreach ($tips as $name => $tiplist) { if ($multiple) { $output .= '
        • '; $output .= ''. $name .':
          '; } $tips = ''; foreach ($tiplist as $tip) { $tips .= '' : '>') . $tip['tip'] . '
        • '; } if ($tips) { $output .= "
            $tips
          "; } if ($multiple) { $output .= ''; } } if ($multiple) { $output .= '
        '; } } return $output; } /** * Format a link to the more extensive filter tips. * * @ingroup themeable */ function theme_filter_tips_more_info() { return '

        '. l(t('More information about formatting options'), 'filter/tips') .'

        '; } /** * @name Standard filters * @{ * Filters implemented by the filter.module. */ /** * Implementation of hook_filter(). Contains a basic set of essential filters. * - HTML filter: * Validates user-supplied HTML, transforming it as necessary. * - PHP evaluator: * Executes PHP code. * - Line break converter: * Converts newlines into paragraph and break tags. */ function filter_filter($op, $delta = 0, $format = -1, $text = '') { switch ($op) { case 'list': return array(0 => t('HTML filter'), 1 => t('PHP evaluator'), 2 => t('Line break converter'), 3 => t('URL filter')); case 'no cache': return $delta == 1; // No caching for the PHP evaluator. case 'description': switch ($delta) { case 0: return t('Allows you to restrict if users can post HTML and which tags to filter out.'); case 1: return t('Runs a piece of PHP code. The usage of this filter should be restricted to administrators only!'); case 2: return t('Converts line breaks into HTML (i.e. <br> and <p> tags).'); case 3: return t('Turns web and e-mail addresses into clickable links.'); default: return; } case 'process': switch ($delta) { case 0: return _filter_html($text, $format); case 1: return drupal_eval($text); case 2: return _filter_autop($text); case 3: return _filter_url($text, $format); default: return $text; } case 'settings': switch ($delta) { case 0: return _filter_html_settings($format); case 3: return _filter_url_settings($format); default: return; } default: return $text; } } /** * Settings for the HTML filter. */ function _filter_html_settings($format) { $form['filter_html'] = array( '#type' => 'fieldset', '#title' => t('HTML filter'), '#collapsible' => TRUE, ); $form['filter_html']["filter_html_$format"] = array( '#type' => 'radios', '#title' => t('Filter HTML tags'), '#default_value' => variable_get("filter_html_$format", FILTER_HTML_STRIP), '#options' => array(FILTER_HTML_STRIP => t('Strip disallowed tags'), FILTER_HTML_ESCAPE => t('Escape all tags')), '#description' => t('How to deal with HTML tags in user-contributed content. If set to "Strip disallowed tags", dangerous tags are removed (see below). If set to "Escape tags", all HTML is escaped and presented as it was typed.'), ); $form['filter_html']["allowed_html_$format"] = array( '#type' => 'textfield', '#title' => t('Allowed HTML tags'), '#default_value' => variable_get("allowed_html_$format", '
          1. '), '#size' => 64, '#maxlength' => 255, '#description' => t('If "Strip disallowed tags" is selected, optionally specify tags which should not be stripped. JavaScript event attributes are always stripped.'), ); $form['filter_html']["filter_html_help_$format"] = array( '#type' => 'checkbox', '#title' => t('Display HTML help'), '#default_value' => variable_get("filter_html_help_$format", 1), '#description' => t('If enabled, Drupal will display some basic HTML help in the long filter tips.'), ); $form['filter_html']["filter_html_nofollow_$format"] = array( '#type' => 'checkbox', '#title' => t('Spam link deterrent'), '#default_value' => variable_get("filter_html_nofollow_$format", FALSE), '#description' => t('If enabled, Drupal will add rel="nofollow" to all links, as a measure to reduce the effectiveness of spam links. Note: this will also prevent valid links from being followed by search engines, therefore it is likely most effective when enabled for anonymous users.'), ); return $form; } /** * HTML filter. Provides filtering of input into accepted HTML. */ function _filter_html($text, $format) { if (variable_get("filter_html_$format", FILTER_HTML_STRIP) == FILTER_HTML_STRIP) { $allowed_tags = preg_split('/\s+|<|>/', variable_get("allowed_html_$format", '
              1. '), -1, PREG_SPLIT_NO_EMPTY); $text = filter_xss($text, $allowed_tags); } if (variable_get("filter_html_$format", FILTER_HTML_STRIP) == FILTER_HTML_ESCAPE) { // Escape HTML $text = check_plain($text); } if (variable_get("filter_html_nofollow_$format", FALSE)) { $text = preg_replace('/]+)>/i', '', $text); } return trim($text); } /** * Settings for URL filter. */ function _filter_url_settings($format) { $form['filter_urlfilter'] = array( '#type' => 'fieldset', '#title' => t('URL filter'), '#collapsible' => TRUE, ); $form['filter_urlfilter']['filter_url_length_'. $format] = array( '#type' => 'textfield', '#title' => t('Maximum link text length'), '#default_value' => variable_get('filter_url_length_'. $format, 72), '#maxlength' => 4, '#description' => t('URLs longer than this number of characters will be truncated to prevent long strings that break formatting. The link itself will be retained; just the text portion of the link will be truncated.'), ); return $form; } /** * URL filter. Automatically converts text web addresses (URLs, e-mail addresses, * ftp links, etc.) into hyperlinks. */ function _filter_url($text, $format) { // Pass length to regexp callback _filter_url_trim(NULL, variable_get('filter_url_length_'. $format, 72)); $text = ' '. $text .' '; // Match absolute URLs. $text = preg_replace_callback("`(

                |

              2. ||[ \n\r\t\(])((http://|https://|ftp://|mailto:|smb://|afp://|file://|gopher://|news://|ssl://|sslv2://|sslv3://|tls://|tcp://|udp://)([a-zA-Z0-9@:%_+*~#?&=.,/;-]*[a-zA-Z0-9@:%_+*~#&=/;-]))([.,?!]*?)(?=(

                |
              3. ||[ \n\r\t\)]))`i", '_filter_url_parse_full_links', $text); // Match e-mail addresses. $text = preg_replace("`(

                |

              4. ||[ \n\r\t\(])([A-Za-z0-9._-]+@[A-Za-z0-9._+-]+\.[A-Za-z]{2,4})([.,?!]*?)(?=(

                |
              5. ||[ \n\r\t\)]))`i", '\1
                \2\3', $text); // Match www domains/addresses. $text = preg_replace_callback("`(

                |

              6. |[ \n\r\t\(])(www\.[a-zA-Z0-9@:%_+*~#?&=.,/;-]*[a-zA-Z0-9@:%_+~#\&=/;-])([.,?!]*?)(?=(

                |
              7. ||[ \n\r\t\)]))`i", '_filter_url_parse_partial_links', $text); $text = substr($text, 1, -1); return $text; } /** * Make links out of absolute URLs. */ function _filter_url_parse_full_links($match) { $match[2] = decode_entities($match[2]); $caption = check_plain(_filter_url_trim($match[2])); $match[2] = check_url($match[2]); return $match[1] . ''. $caption .''. $match[5]; } /** * Make links out of domain names starting with "www." */ function _filter_url_parse_partial_links($match) { $match[2] = decode_entities($match[2]); $caption = check_plain(_filter_url_trim($match[2])); $match[2] = check_plain($match[2]); return $match[1] . ''. $caption .''. $match[3]; } /** * Shortens long URLs to http://www.example.com/long/url... */ function _filter_url_trim($text, $length = NULL) { static $_length; if ($length !== NULL) { $_length = $length; } if (strlen($text) > $_length) { $text = substr($text, 0, $_length) .'...'; } return $text; } /** * Convert line breaks into

                and
                in an intelligent fashion. * Based on: http://photomatt.net/scripts/autop */ function _filter_autop($text) { // All block level tags $block = '(?:table|thead|tfoot|caption|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|select|form|blockquote|address|p|h[1-6])'; // Split at

                , ,  tags.
                  // We don't apply any processing to the contents of these tags to avoid messing
                  // up code. We look for matched pairs and allow basic nesting. For example:
                  // "processed 
                 ignored  ignored 
                processed" $chunks = preg_split('@(]*>)@i', $text, -1, PREG_SPLIT_DELIM_CAPTURE); // Note: PHP ensures the array consists of alternating delimiters and literals // and begins and ends with a literal (inserting NULL as required). $ignore = FALSE; $ignoretag = ''; $output = ''; foreach ($chunks as $i => $chunk) { if ($i % 2) { // Opening or closing tag? $open = ($chunk[1] != '/'); list($tag) = split('[ >]', substr($chunk, 2 - $open), 2); if (!$ignore) { if ($open) { $ignore = TRUE; $ignoretag = $tag; } } // Only allow a matching tag to close it. else if (!$open && $ignoretag == $tag) { $ignore = FALSE; $ignoretag = ''; } } else if (!$ignore) { $chunk = preg_replace('|\n*$|', '', $chunk) ."\n\n"; // just to make things a little easier, pad the end $chunk = preg_replace('|
                \s*
                |', "\n\n", $chunk); $chunk = preg_replace('!(<'. $block .'[^>]*>)!', "\n$1", $chunk); // Space things out a little $chunk = preg_replace('!()!', "$1\n\n", $chunk); // Space things out a little $chunk = preg_replace("/\n\n+/", "\n\n", $chunk); // take care of duplicates $chunk = preg_replace('/\n?(.+?)(?:\n\s*\n|\z)/s', "

                $1

                \n", $chunk); // make paragraphs, including one at the end $chunk = preg_replace('|

                \s*

                \n|', '', $chunk); // under certain strange conditions it could create a P of entirely whitespace $chunk = preg_replace("|

                (|", "$1", $chunk); // problem with nested lists $chunk = preg_replace('|

                ]*)>|i', "

                ", $chunk); $chunk = str_replace('

                ', '

                ', $chunk); $chunk = preg_replace('!

                \s*(]*>)!', "$1", $chunk); $chunk = preg_replace('!(]*>)\s*

                !', "$1", $chunk); $chunk = preg_replace('|(?)\s*\n|', "
                \n", $chunk); // make line breaks $chunk = preg_replace('!(]*>)\s*
                !', "$1", $chunk); $chunk = preg_replace('!
                (\s*)!', '$1', $chunk); $chunk = preg_replace('/&([^#])(?![A-Za-z0-9]{1,8};)/', '&$1', $chunk); } $output .= $chunk; } return $output; } /** * Very permissive XSS/HTML filter for admin-only use. * * Use only for fields where it is impractical to use the * whole filter system, but where some (mainly inline) mark-up * is desired (so check_plain() is not acceptable). * * Allows all tags that can be used inside an HTML body, save * for scripts and styles. */ function filter_xss_admin($string) { return filter_xss($string, array('a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'div', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd', 'li', 'object', 'ol', 'p', 'param', 'pre', 'q', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'ul', 'var')); } /** * Filters XSS. Based on kses by Ulf Harnhammar, see * http://sourceforge.net/projects/kses * * For examples of various XSS attacks, see: * http://ha.ckers.org/xss.html * * This code does four things: * - Removes characters and constructs that can trick browsers * - Makes sure all HTML entities are well-formed * - Makes sure all HTML tags and attributes are well-formed * - Makes sure no HTML tags contain URLs with a disallowed protocol (e.g. javascript:) * * @param $string * The string with raw HTML in it. It will be stripped of everything that can cause * an XSS attack. * @param $allowed_tags * An array of allowed tags. * @param $format * The format to use. */ function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd')) { // Only operate on valid UTF-8 strings. This is necessary to prevent cross // site scripting issues on Internet Explorer 6. if (!drupal_validate_utf8($string)) { return ''; } // Store the input format _filter_xss_split($allowed_tags, TRUE); // Remove NUL characters (ignored by some browsers) $string = str_replace(chr(0), '', $string); // Remove Netscape 4 JS entities $string = preg_replace('%&\s*\{[^}]*(\}\s*;?|$)%', '', $string); // Defuse all HTML entities $string = str_replace('&', '&', $string); // Change back only well-formed entities in our whitelist // Named entities $string = preg_replace('/&([A-Za-z][A-Za-z0-9]*;)/', '&\1', $string); // Decimal numeric entities $string = preg_replace('/&#([0-9]+;)/', '&#\1', $string); // Hexadecimal numeric entities $string = preg_replace('/&#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/', '&#x\1', $string); return preg_replace_callback('% ( <(?=[^a-zA-Z!/]) # a lone < | # or <[^>]*.(>|$) # a string that starts with a <, up until the > or the end of the string | # or > # just a > )%x', '_filter_xss_split', $string); } /** * Processes an HTML tag. * * @param @m * An array with various meaning depending on the value of $store. * If $store is TRUE then the array contains the allowed tags. * If $store is FALSE then the array has one element, the HTML tag to process. * @param $store * Whether to store $m. * @return * If the element isn't allowed, an empty string. Otherwise, the cleaned up * version of the HTML element. */ function _filter_xss_split($m, $store = FALSE) { static $allowed_html; if ($store) { $allowed_html = array_flip($m); return; } $string = $m[1]; if (substr($string, 0, 1) != '<') { // We matched a lone ">" character return '>'; } else if (strlen($string) == 1) { // We matched a lone "<" character return '<'; } if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?$%', $string, $matches)) { // Seriously malformed return ''; } $slash = trim($matches[1]); $elem = &$matches[2]; $attrlist = &$matches[3]; if (!isset($allowed_html[strtolower($elem)])) { // Disallowed HTML element return ''; } if ($slash != '') { return ""; } // Is there a closing XHTML slash at the end of the attributes? // In PHP 5.1.0+ we could count the changes, currently we need a separate match $xhtml_slash = preg_match('%\s?/\s*$%', $attrlist) ? ' /' : ''; $attrlist = preg_replace('%(\s?)/\s*$%', '\1', $attrlist); // Clean up attributes $attr2 = implode(' ', _filter_xss_attributes($attrlist)); $attr2 = preg_replace('/[<>]/', '', $attr2); $attr2 = strlen($attr2) ? ' '. $attr2 : ''; return "<$elem$attr2$xhtml_slash>"; } /** * Processes a string of HTML attributes. * * @return * Cleaned up version of the HTML attributes. */ function _filter_xss_attributes($attr) { $attrarr = array(); $mode = 0; $attrname = ''; while (strlen($attr) != 0) { // Was the last operation successful? $working = 0; switch ($mode) { case 0: // Attribute name, href for instance if (preg_match('/^([-a-zA-Z]+)/', $attr, $match)) { $attrname = strtolower($match[1]); $skip = ($attrname == 'style' || substr($attrname, 0, 2) == 'on'); $working = $mode = 1; $attr = preg_replace('/^[-a-zA-Z]+/', '', $attr); } break; case 1: // Equals sign or valueless ("selected") if (preg_match('/^\s*=\s*/', $attr)) { $working = 1; $mode = 2; $attr = preg_replace('/^\s*=\s*/', '', $attr); break; } if (preg_match('/^\s+/', $attr)) { $working = 1; $mode = 0; if (!$skip) { $attrarr[] = $attrname; } $attr = preg_replace('/^\s+/', '', $attr); } break; case 2: // Attribute value, a URL after href= for instance if (preg_match('/^"([^"]*)"(\s+|$)/', $attr, $match)) { $thisval = filter_xss_bad_protocol($match[1]); if (!$skip) { $attrarr[] = "$attrname=\"$thisval\""; } $working = 1; $mode = 0; $attr = preg_replace('/^"[^"]*"(\s+|$)/', '', $attr); break; } if (preg_match("/^'([^']*)'(\s+|$)/", $attr, $match)) { $thisval = filter_xss_bad_protocol($match[1]); if (!$skip) { $attrarr[] = "$attrname='$thisval'";; } $working = 1; $mode = 0; $attr = preg_replace("/^'[^']*'(\s+|$)/", '', $attr); break; } if (preg_match("%^([^\s\"']+)(\s+|$)%", $attr, $match)) { $thisval = filter_xss_bad_protocol($match[1]); if (!$skip) { $attrarr[] = "$attrname=\"$thisval\""; } $working = 1; $mode = 0; $attr = preg_replace("%^[^\s\"']+(\s+|$)%", '', $attr); } break; } if ($working == 0) { // not well formed, remove and try again $attr = preg_replace('/ ^ ( "[^"]*("|$) # - a string that starts with a double quote, up until the next double quote or the end of the string | # or \'[^\']*(\'|$)| # - a string that starts with a quote, up until the next quote or the end of the string | # or \S # - a non-whitespace character )* # any number of the above three \s* # any number of whitespaces /x', '', $attr); $mode = 0; } } // the attribute list ends with a valueless attribute like "selected" if ($mode == 1) { $attrarr[] = $attrname; } return $attrarr; } /** * Processes an HTML attribute value and ensures it does not contain an URL * with a disallowed protocol (e.g. javascript:) * * @param $string * The string with the attribute value. * @param $decode * Whether to decode entities in the $string. Set to FALSE if the $string * is in plain text, TRUE otherwise. Defaults to TRUE. * @return * Cleaned up and HTML-escaped version of $string. */ function filter_xss_bad_protocol($string, $decode = TRUE) { static $allowed_protocols; if (!isset($allowed_protocols)) { $allowed_protocols = array_flip(variable_get('filter_allowed_protocols', array('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'mailto', 'irc', 'ssh', 'sftp', 'webcal'))); } // Get the plain text representation of the attribute value (i.e. its meaning). if ($decode) { $string = decode_entities($string); } // Iteratively remove any invalid protocol found. do { $before = $string; $colonpos = strpos($string, ':'); if ($colonpos > 0) { // We found a colon, possibly a protocol. Verify. $protocol = substr($string, 0, $colonpos); // If a colon is preceded by a slash, question mark or hash, it cannot // possibly be part of the URL scheme. This must be a relative URL, // which inherits the (safe) protocol of the base document. if (preg_match('![/?#]!', $protocol)) { break; } // Per RFC2616, section 3.2.3 (URI Comparison) scheme comparison must be case-insensitive. // Check if this is a disallowed protocol. if (!isset($allowed_protocols[strtolower($protocol)])) { $string = substr($string, $colonpos + 1); } } } while ($before != $string); return check_plain($string); } /** * @} End of "Standard filters". */ loki_website/modules/filter/filter.info0000644000004100000410000000047510741515024020672 0ustar www-datawww-data; $Id: filter.info,v 1.3 2006/11/21 20:55:34 dries Exp $ name = Filter description = Handles the filtering of content in preparation for display. package = Core - required version = VERSION ; Information added by drupal.org packaging script on 2008-01-10 version = "5.6" project = "drupal" datestamp = "1200003604" loki_website/modules/book/0000755000004100000410000000000010744012100016155 5ustar www-datawww-dataloki_website/modules/book/book.module0000644000004100000410000011016110763105307020333 0ustar www-datawww-data array( 'name' => t('Book page'), 'module' => 'book', 'description' => t("A book is a collaborative writing effort: users can collaborate writing the pages of the book, positioning the pages in the right order, and reviewing or modifying pages previously written. So when you have some information to share or when you read a page of the book and you didn't like it, or if you think a certain page could have been written better, you can do something about it."), ) ); } /** * Implementation of hook_perm(). */ function book_perm() { return array('outline posts in books', 'create book pages', 'create new books', 'edit book pages', 'edit own book pages', 'see printer-friendly version'); } /** * Implementation of hook_access(). */ function book_access($op, $node) { global $user; if ($op == 'create') { // Only registered users can create book pages. Given the nature // of the book module this is considered to be a good/safe idea. return user_access('create book pages'); } if ($op == 'update') { // Only registered users can update book pages. Given the nature // of the book module this is considered to be a good/safe idea. // One can only update a book page if there are no suggested updates // of that page waiting for approval. That is, only updates that // don't overwrite the current or pending information are allowed. if (user_access('edit book pages') || ($node->uid == $user->uid && user_access('edit own book pages'))) { return TRUE; } else { // do nothing. node-access() will determine further access } } } /** * Implementation of hook_link(). */ function book_link($type, $node = NULL, $teaser = FALSE) { $links = array(); if ($type == 'node' && isset($node->parent)) { if (!$teaser) { if (book_access('create', $node) && $node->status == 1) { $links['book_add_child'] = array( 'title' => t('Add child page'), 'href' => "node/add/book/parent/$node->nid" ); } if (user_access('see printer-friendly version')) { $links['book_printer'] = array( 'title' => t('Printer-friendly version'), 'href' => 'book/export/html/'. $node->nid, 'attributes' => array('title' => t('Show a printer-friendly version of this book page and its sub-pages.')) ); } } } return $links; } /** * Implementation of hook_menu(). */ function book_menu($may_cache) { $items = array(); if ($may_cache) { $items[] = array( 'path' => 'admin/content/book', 'title' => t('Books'), 'description' => t("Manage site's books and orphaned book pages."), 'callback' => 'book_admin', 'access' => user_access('administer nodes')); $items[] = array( 'path' => 'admin/content/book/list', 'title' => t('List'), 'type' => MENU_DEFAULT_LOCAL_TASK); $items[] = array( 'path' => 'admin/content/book/orphan', 'title' => t('Orphan pages'), 'callback' => 'drupal_get_form', 'callback arguments' => array('book_admin_orphan'), 'type' => MENU_LOCAL_TASK, 'weight' => 8); $items[] = array( 'path' => 'book', 'title' => t('Books'), 'callback' => 'book_render', 'access' => user_access('access content'), 'type' => MENU_SUGGESTED_ITEM); $items[] = array( 'path' => 'book/export', 'callback' => 'book_export', 'access' => user_access('access content'), 'type' => MENU_CALLBACK); } else { // Add the CSS for this module // We put this in !$may_cache so it's only added once per request drupal_add_css(drupal_get_path('module', 'book') .'/book.css'); // To avoid SQL overhead, check whether we are on a node page and whether the // user is allowed to outline posts in books. if (arg(0) == 'node' && is_numeric(arg(1)) && user_access('outline posts in books')) { // Only add the outline-tab for non-book pages: $result = db_query(db_rewrite_sql("SELECT n.nid FROM {node} n WHERE n.nid = %d AND n.type != 'book'"), arg(1)); if (db_num_rows($result) > 0) { $items[] = array( 'path' => 'node/'. arg(1) .'/outline', 'title' => t('Outline'), 'callback' => 'drupal_get_form', 'callback arguments' => array('book_outline', arg(1)), 'access' => user_access('outline posts in books'), 'type' => MENU_LOCAL_TASK, 'weight' => 2); } } } return $items; } /** * Implementation of hook_block(). * * Displays the book table of contents in a block when the current page is a * single-node view of a book node. */ function book_block($op = 'list', $delta = 0) { $block = array(); if ($op == 'list') { $block[0]['info'] = t('Book navigation'); return $block; } else if ($op == 'view') { // Only display this block when the user is browsing a book: if (arg(0) == 'node' && is_numeric(arg(1))) { $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.nid = %d'), arg(1)); if (db_num_rows($result) > 0) { $node = db_fetch_object($result); $path = book_location($node); $path[] = $node; $expand = array(); foreach ($path as $key => $node) { $expand[] = $node->nid; } $block['subject'] = check_plain($path[0]->title); $block['content'] = book_tree($expand[0], 5, $expand); } } return $block; } } /** * Implementation of hook_insert(). */ function book_insert($node) { db_query("INSERT INTO {book} (nid, vid, parent, weight) VALUES (%d, %d, %d, %d)", $node->nid, $node->vid, $node->parent, $node->weight); } /** * Implementation of hook_submit(). */ function book_submit(&$node) { global $user; // Set default values for non-administrators. if (!user_access('administer nodes')) { $node->revision = 1; $node->uid = $user->uid; } } /** * Implementation of hook_form(). */ function book_form(&$node) { $type = node_get_types('type', $node); if ($node->nid && !$node->parent && !user_access('create new books')) { $form['parent'] = array('#type' => 'value', '#value' => $node->parent); } else { $form['parent'] = array('#type' => 'select', '#title' => t('Parent'), '#default_value' => ($node->parent ? $node->parent : arg(4)), '#options' => book_toc($node->nid), '#weight' => -4, '#description' => user_access('create new books') ? t('The parent section in which to place this page. Note that each page whose parent is <top-level> is an independent, top-level book.') : t('The parent that this page belongs in.'), ); } $form['title'] = array('#type' => 'textfield', '#title' => check_plain($type->title_label), '#required' => TRUE, '#default_value' => $node->title, '#weight' => -5, ); $form['body_filter']['body'] = array('#type' => 'textarea', '#title' => check_plain($type->body_label), '#default_value' => $node->body, '#rows' => 20, '#required' => TRUE, ); $form['body_filter']['format'] = filter_form($node->format); if (user_access('administer nodes')) { $form['weight'] = array('#type' => 'weight', '#title' => t('Weight'), '#default_value' => $node->weight, '#delta' => 15, '#weight' => 5, '#description' => t('Pages at a given level are ordered first by weight and then by title.'), ); } else { // If a regular user updates a book page, we preserve the node weight; otherwise // we use 0 as the default for new pages $form['weight'] = array( '#type' => 'value', '#value' => isset($node->weight) ? $node->weight : 0, ); } return $form; } /** * Implementation of function book_outline() * Handles all book outline operations. */ function book_outline($nid) { $node = node_load($nid); $form['parent'] = array('#type' => 'select', '#title' => t('Parent'), '#default_value' => $node->parent, '#options' => book_toc($node->nid), '#description' => t('The parent page in the book.'), ); $form['weight'] = array('#type' => 'weight', '#title' => t('Weight'), '#default_value' => $node->weight, '#delta' => 15, '#description' => t('Pages at a given level are ordered first by weight and then by title.'), ); $form['log'] = array('#type' => 'textarea', '#title' => t('Log message'), '#description' => t('An explanation to help other authors understand your motivations to put this post into the book.'), ); $form['nid'] = array('#type' => 'value', '#value' => $nid); if (isset($node->parent)) { $form['update'] = array('#type' => 'submit', '#value' => t('Update book outline'), ); $form['remove'] = array('#type' => 'submit', '#value' => t('Remove from book outline'), ); } else { $form['add'] = array('#type' => 'submit', '#value' => t('Add to book outline')); } drupal_set_title(check_plain($node->title)); return $form; } /** * Handles book outline form submissions. */ function book_outline_submit($form_id, $form_values) { $op = $form_values['op']; $node = node_load($form_values['nid']); switch ($op) { case t('Add to book outline'): db_query('INSERT INTO {book} (nid, vid, parent, weight) VALUES (%d, %d, %d, %d)', $node->nid, $node->vid, $form_values['parent'], $form_values['weight']); db_query("UPDATE {node_revisions} SET log = '%s' WHERE vid = %d", $form_values['log'], $node->vid); drupal_set_message(t('The post has been added to the book.')); break; case t('Update book outline'): db_query('UPDATE {book} SET parent = %d, weight = %d WHERE vid = %d', $form_values['parent'], $form_values['weight'], $node->vid); db_query("UPDATE {node_revisions} SET log = '%s' WHERE vid = %d", $form_values['log'], $node->vid); drupal_set_message(t('The book outline has been updated.')); break; case t('Remove from book outline'): db_query('DELETE FROM {book} WHERE nid = %d', $node->nid); drupal_set_message(t('The post has been removed from the book.')); break; } return "node/$node->nid"; } /** * Given a node, this function returns an array of 'book node' objects * representing the path in the book tree from the root to the * parent of the given node. * * @param $node * A book node object for which to compute the path. * * @return * An array of book node objects representing the path nodes root to * parent of the given node. Returns an empty array if the node does * not exist or is not part of a book hierarchy. */ function book_location($node, $nodes = array()) { $parent = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.nid = %d'), $node->parent)); if (isset($parent->title)) { $nodes = book_location($parent, $nodes); $nodes[] = $parent; } return $nodes; } /** * Given a node, this function returns an array of 'book node' objects * representing the path in the book tree from the given node down to * the last sibling of it. * * @param $node * A book node object where the path starts. * * @return * An array of book node objects representing the path nodes from the * given node. Returns an empty array if the node does not exist or * is not part of a book hierarchy or there are no siblings. */ function book_location_down($node, $nodes = array()) { $last_direct_child = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.status = 1 AND b.parent = %d ORDER BY b.weight DESC, n.title DESC'), $node->nid)); if ($last_direct_child) { $nodes[] = $last_direct_child; $nodes = book_location_down($last_direct_child, $nodes); } return $nodes; } /** * Fetches the node object of the previous page of the book. */ function book_prev($node) { // If the parent is zero, we are at the start of a book so there is no previous. if ($node->parent == 0) { return NULL; } // Previous on the same level: $direct_above = db_fetch_object(db_query(db_rewrite_sql("SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = %d AND n.status = 1 AND (b.weight < %d OR (b.weight = %d AND n.title < '%s')) ORDER BY b.weight DESC, n.title DESC"), $node->parent, $node->weight, $node->weight, $node->title)); if ($direct_above) { // Get last leaf of $above. $path = book_location_down($direct_above); return $path ? (count($path) > 0 ? array_pop($path) : NULL) : $direct_above; } else { // Direct parent: $prev = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.nid = %d AND n.status = 1'), $node->parent)); return $prev; } } /** * Fetches the node object of the next page of the book. */ function book_next($node) { // get first direct child $child = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = %d AND n.status = 1 ORDER BY b.weight ASC, n.title ASC'), $node->nid)); if ($child) { return $child; } // No direct child: get next for this level or any parent in this book. $path = book_location($node); // Path to top-level node including this one. $path[] = $node; while (($leaf = array_pop($path)) && count($path)) { $next = db_fetch_object(db_query(db_rewrite_sql("SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = %d AND n.status = 1 AND (b.weight > %d OR (b.weight = %d AND n.title > '%s')) ORDER BY b.weight ASC, n.title ASC"), $leaf->parent, $leaf->weight, $leaf->weight, $leaf->title)); if ($next) { return $next; } } } /** * Returns the content of a given node. If $teaser if TRUE, returns * the teaser rather than full content. Displays the most recently * approved revision of a node (if any) unless we have to display this * page in the context of the moderation queue. */ function book_content($node, $teaser = FALSE) { // Return the page body. return node_prepare($node, $teaser); } /** * Implementation of hook_nodeapi(). * * Appends book navigation to all nodes in the book. */ function book_nodeapi(&$node, $op, $teaser, $page) { switch ($op) { case 'load': return db_fetch_array(db_query('SELECT parent, weight FROM {book} WHERE vid = %d', $node->vid)); break; case 'view': if (!$teaser) { if (isset($node->parent)) { $path = book_location($node); // Construct the breadcrumb: $node->breadcrumb = array(); // Overwrite the trail with a book trail. foreach ($path as $level) { $node->breadcrumb[] = array('path' => 'node/'. $level->nid, 'title' => $level->title); } $node->breadcrumb[] = array('path' => 'node/'. $node->nid); $node->content['book_navigation'] = array( '#value' => theme('book_navigation', $node), // changed by DROR //'#weight' => 100, '#weight' => -1, ); if ($page) { menu_set_location($node->breadcrumb); } } } break; case 'update': if (isset($node->parent)) { if ($node->revision) { db_query("INSERT INTO {book} (nid, vid, parent, weight) VALUES (%d, %d, %d, %d)", $node->nid, $node->vid, $node->parent, $node->weight); } else { db_query("UPDATE {book} SET parent = %d, weight = %d WHERE vid = %d", $node->parent, $node->weight, $node->vid); } } break; case 'delete revision': db_query('DELETE FROM {book} WHERE vid = %d', $node->vid); break; case 'delete': db_query('DELETE FROM {book} WHERE nid = %d', $node->nid); break; } } /** * Prepares the links to children (TOC) and forward/backward * navigation for a node presented as a book page. * * @ingroup themeable * changed by DROR */ function theme_book_navigation($node) { $output = ''; $links = ''; if ($node->nid) { // $tree = book_tree($node->nid); if ($prev = book_prev($node)) { drupal_add_link(array('rel' => 'prev', 'href' => url('node/'. $prev->nid))); $links .= l(t('‹ ') . $prev->title, 'node/'. $prev->nid, array('class' => 'page-previous', 'title' => t('Go to previous page'))); } if ($node->parent) { drupal_add_link(array('rel' => 'up', 'href' => url('node/'. $node->parent))); $links .= l(t('up'), 'node/'. $node->parent, array('class' => 'page-up', 'title' => t('Go to parent page'))); } if ($next = book_next($node)) { drupal_add_link(array('rel' => 'next', 'href' => url('node/'. $next->nid))); $links .= l($next->title . t(' ›'), 'node/'. $next->nid, array('class' => 'page-next', 'title' => t('Go to next page'))); } if (isset($tree) || isset($links)) { $output = '
                '; if (isset($tree)) { $output .= $tree; } if (isset($links)) { $output .= ''; } $output .= '
                '; } } return $output; } /** * This is a helper function for book_toc(). */ function book_toc_recurse($nid, $indent, $toc, $children, $exclude) { if ($children[$nid]) { foreach ($children[$nid] as $foo => $node) { if (!$exclude || $exclude != $node->nid) { $toc[$node->nid] = $indent .' '. $node->title; $toc = book_toc_recurse($node->nid, $indent .'--', $toc, $children, $exclude); } } } return $toc; } /** * Returns an array of titles and nid entries of book pages in table of contents order. */ function book_toc($exclude = 0) { $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.status = 1 ORDER BY b.weight, n.title')); $children = array(); while ($node = db_fetch_object($result)) { if (!$children[$node->parent]) { $children[$node->parent] = array(); } $children[$node->parent][] = $node; } $toc = array(); // If the user has permission to create new books, add the top-level book page to the menu; if (user_access('create new books')) { $toc[0] = '<'. t('top-level') .'>'; } $toc = book_toc_recurse(0, '', $toc, $children, $exclude); return $toc; } /** * This is a helper function for book_tree() */ function book_tree_recurse($nid, $depth, $children, $unfold = array()) { $output = ''; if ($depth > 0) { if (isset($children[$nid])) { foreach ($children[$nid] as $foo => $node) { if (in_array($node->nid, $unfold)) { if ($tree = book_tree_recurse($node->nid, $depth - 1, $children, $unfold)) { $output .= '
              8. '; $output .= l($node->title, 'node/'. $node->nid); $output .= ''; $output .= '
              9. '; } else { $output .= '
              10. '. l($node->title, 'node/'. $node->nid) .'
              11. '; } } else { if ($tree = book_tree_recurse($node->nid, 1, $children)) { $output .= ''; } else { $output .= '
              12. '. l($node->title, 'node/'. $node->nid) .'
              13. '; } } } } } return $output; } /** * Returns an HTML nested list (wrapped in a menu-class div) representing the book nodes * as a tree. */ function book_tree($parent = 0, $depth = 3, $unfold = array()) { $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.status = 1 ORDER BY b.weight, n.title')); while ($node = db_fetch_object($result)) { $list = isset($children[$node->parent]) ? $children[$node->parent] : array(); $list[] = $node; $children[$node->parent] = $list; } if ($tree = book_tree_recurse($parent, $depth, $children, $unfold)) { return ''; } } /** * Menu callback; prints a listing of all books. */ function book_render() { $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = 0 AND n.status = 1 ORDER BY b.weight, n.title')); $books = array(); while ($node = db_fetch_object($result)) { $books[] = l($node->title, 'node/'. $node->nid); } return theme('item_list', $books); } /** * Menu callback; Generates various representation of a book page with * all descendants and prints the requested representation to output. * * The function delegates the generation of output to helper functions. * The function name is derived by prepending 'book_export_' to the * given output type. So, e.g., a type of 'html' results in a call to * the function book_export_html(). * * @param type * - a string encoding the type of output requested. * The following types are currently supported in book module * html: HTML (printer friendly output) * Other types are supported in contributed modules. * @param nid * - an integer representing the node id (nid) of the node to export * */ function book_export($type = 'html', $nid = 0) { $type = drupal_strtolower($type); $node_result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.nid = %d'), $nid); if (db_num_rows($node_result) > 0) { $node = db_fetch_object($node_result); } $depth = count(book_location($node)) + 1; $export_function = 'book_export_'. $type; if (function_exists($export_function)) { print call_user_func($export_function, $nid, $depth); } else { drupal_set_message(t('Unknown export format.')); drupal_not_found(); } } /** * This function is called by book_export() to generate HTML for export. * * The given node is /embedded to its absolute depth in a top level * section/. For example, a child node with depth 2 in the hierarchy * is contained in (otherwise empty) <div> elements * corresponding to depth 0 and depth 1. This is intended to support * WYSIWYG output - e.g., level 3 sections always look like level 3 * sections, no matter their depth relative to the node selected to be * exported as printer-friendly HTML. * * @param nid * - an integer representing the node id (nid) of the node to export * @param depth * - an integer giving the depth in the book hierarchy of the node * which is to be exported * * @return * - string containing HTML representing the node and its children in * the book hierarchy */ function book_export_html($nid, $depth) { if (user_access('see printer-friendly version')) { $node = node_load($nid); for ($i = 1; $i < $depth; $i++) { $content .= "
                \n"; } $content .= book_recurse($nid, $depth, 'book_node_visitor_html_pre', 'book_node_visitor_html_post'); for ($i = 1; $i < $depth; $i++) { $content .= "
                \n"; } return theme('book_export_html', check_plain($node->title), $content); } else { drupal_access_denied(); } } /** * How the book's HTML export should be themed * * @ingroup themeable */ function theme_book_export_html($title, $content) { global $base_url; $html = "\n"; $html .= ''; $html .= "\n". $title ."\n"; $html .= ''; $html .= '' . "\n"; $html .= "\n"; $html .= "\n\n". $content ."\n\n\n"; return $html; } /** * Traverses the book tree. Applies the $visit_pre() callback to each * node, is called recursively for each child of the node (in weight, * title order). Finally appends the output of the $visit_post() * callback to the output before returning the generated output. * * @todo This is duplicitous with node_build_content(). * * @param nid * - the node id (nid) of the root node of the book hierarchy. * @param depth * - the depth of the given node in the book hierarchy. * @param visit_pre * - a function callback to be called upon visiting a node in the tree * @param visit_post * - a function callback to be called after visiting a node in the tree, * but before recursively visiting children. * @return * - the output generated in visiting each node */ function book_recurse($nid = 0, $depth = 1, $visit_pre, $visit_post) { $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.status = 1 AND n.nid = %d ORDER BY b.weight, n.title'), $nid); while ($page = db_fetch_object($result)) { // Load the node: $node = node_load($page->nid); if ($node) { if (function_exists($visit_pre)) { $output .= call_user_func($visit_pre, $node, $depth, $nid); } else { $output .= book_node_visitor_html_pre($node, $depth, $nid); } $children = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.status = 1 AND b.parent = %d ORDER BY b.weight, n.title'), $node->nid); while ($childpage = db_fetch_object($children)) { $childnode = node_load($childpage->nid); if ($childnode->nid != $node->nid) { $output .= book_recurse($childnode->nid, $depth + 1, $visit_pre, $visit_post); } } if (function_exists($visit_post)) { $output .= call_user_func($visit_post, $node, $depth); } else { # default $output .= book_node_visitor_html_post($node, $depth); } } } return $output; } /** * Generates printer-friendly HTML for a node. This function * is a 'pre-node' visitor function for book_recurse(). * * @param $node * - the node to generate output for. * @param $depth * - the depth of the given node in the hierarchy. This * is used only for generating output. * @param $nid * - the node id (nid) of the given node. This * is used only for generating output. * @return * - the HTML generated for the given node. */ function book_node_visitor_html_pre($node, $depth, $nid) { // Remove the delimiter (if any) that separates the teaser from the body. $node->body = str_replace('', '', $node->body); // The 'view' hook can be implemented to overwrite the default function // to display nodes. if (node_hook($node, 'view')) { $node = node_invoke($node, 'view', FALSE, FALSE); } else { $node = node_prepare($node, FALSE); } // Allow modules to make their own additions to the node. node_invoke_nodeapi($node, 'print'); $output .= "
                nid ."\" class=\"section-$depth\">\n"; $output .= "

                ". check_plain($node->title) ."

                \n"; $output .= drupal_render($node->content); return $output; } /** * Finishes up generation of printer-friendly HTML after visiting a * node. This function is a 'post-node' visitor function for * book_recurse(). */ function book_node_visitor_html_post($node, $depth) { return "
                \n"; } function _book_admin_table($nodes = array()) { $form = array( '#theme' => 'book_admin_table', '#tree' => TRUE, ); foreach ($nodes as $node) { $form = array_merge($form, _book_admin_table_tree($node, 0)); } return $form; } function _book_admin_table_tree($node, $depth) { $form = array(); $form[] = array( 'nid' => array('#type' => 'value', '#value' => $node->nid), 'depth' => array('#type' => 'value', '#value' => $depth), 'title' => array( '#type' => 'textfield', '#default_value' => $node->title, '#maxlength' => 255, ), 'weight' => array( '#type' => 'weight', '#default_value' => $node->weight, '#delta' => 15, ), ); $children = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = %d ORDER BY b.weight, n.title'), $node->nid); while ($child = db_fetch_object($children)) { $form = array_merge($form, _book_admin_table_tree(node_load($child->nid), $depth + 1)); } return $form; } function theme_book_admin_table($form) { $header = array(t('Title'), t('Weight'), array('data' => t('Operations'), 'colspan' => '3')); $rows = array(); foreach (element_children($form) as $key) { $nid = $form[$key]['nid']['#value']; $pid = $form[0]['nid']['#value']; $rows[] = array( '
                '. drupal_render($form[$key]['title']) .'
                ', drupal_render($form[$key]['weight']), l(t('view'), 'node/'. $nid), l(t('edit'), 'node/'. $nid .'/edit'), l(t('delete'), 'node/'. $nid .'/delete', NULL, 'destination=admin/content/book'. (arg(3) == 'orphan' ? '/orphan' : '') . ($pid != $nid ? '/'.$pid : '')) ); } return theme('table', $header, $rows); } /** * Display an administrative view of the hierarchy of a book. */ function book_admin_edit($nid) { $node = node_load($nid); if ($node->nid) { drupal_set_title(check_plain($node->title)); $form = array(); $form['table'] = _book_admin_table(array($node)); $form['save'] = array( '#type' => 'submit', '#value' => t('Save book pages'), ); return $form; } else { drupal_not_found(); } } /** * Menu callback; displays a listing of all orphaned book pages. */ function book_admin_orphan() { $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, n.status, b.parent FROM {node} n INNER JOIN {book} b ON n.vid = b.vid')); $pages = array(); while ($page = db_fetch_object($result)) { $pages[$page->nid] = $page; } $orphans = array(); if (count($pages)) { foreach ($pages as $page) { if ($page->parent && empty($pages[$page->parent])) { $orphans[] = node_load($page->nid); } } } if (count($orphans)) { $form['table'] = _book_admin_table($orphans); $form['save'] = array( '#type' => 'submit', '#value' => t('Save book pages'), ); } else { $form['error'] = array('#value' => '

                '. t('There are no orphan pages.') .'

                '); } $form['#base'] = 'book_admin_edit'; return $form; } function book_admin_edit_submit($form_id, $form_values) { foreach ($form_values['table'] as $row) { $node = node_load($row['nid']); if ($row['title'] != $node->title || $row['weight'] != $node->weight) { $node->title = $row['title']; $node->weight = $row['weight']; node_save($node); watchdog('content', t('%type: updated %title.', array('%type' => t('book'), '%title' => $node->title)), WATCHDOG_NOTICE, l(t('view'), 'node/'. $node->nid)); } } if (is_numeric(arg(3))) { // Updating pages in a single book. $book = node_load(arg(3)); drupal_set_message(t('Updated book %title.', array('%title' => $book->title))); } else { // Updating the orphan pages. drupal_set_message(t('Updated orphan book pages.')); } } /** * Menu callback; displays the book administration page. */ function book_admin($nid = 0) { if ($nid) { return drupal_get_form('book_admin_edit', $nid); } else { return book_admin_overview(); } } /** * Returns an administrative overview of all books. */ function book_admin_overview() { $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = 0 ORDER BY b.weight, n.title')); while ($book = db_fetch_object($result)) { $rows[] = array(l($book->title, "node/$book->nid"), l(t('outline'), "admin/content/book/$book->nid")); } $headers = array(t('Book'), t('Operations')); return theme('table', $headers, $rows); } /** * Implementation of hook_help(). */ function book_help($section) { switch ($section) { case 'admin/help#book': $output = '

                '. t('The book module is suited for creating structured, multi-page hypertexts such as site resource guides, manuals, and Frequently Asked Questions (FAQs). It permits a document to have chapters, sections, subsections, etc. Authors with suitable permissions can add pages to a collaborative book, placing them into the existing document by adding them to a table of contents menu.') .'

                '; $output .= '

                '. t('Book pages have navigation elements at the bottom of the page for moving through the text. These link to the previous and next pages in the book, as well as a link labeled up, leading to the level above in the structure. More comprehensive navigation may be provided by enabling the book navigation block on the block administration page.', array('@admin-block' => url('admin/build/block'))) .'

                '; $output .= '

                '. t('Users can select the printer-friendly version link visible at the bottom of a book page to generate a printer-friendly display of the page and all of its subsections. ') .'

                '; $output .= '

                '. t("Posts of type %book are automatically added to the book hierarchy. Users with the outline posts in books permission can also add content of any other type to a book, placing it into the existing book structure through the interface that's available by clicking on the outline tab while viewing that post.", array('%book' => node_get_types('name', 'book'))) .'

                '; $output .= '

                '. t('Administrators can view a list of all books on the book administration page. In this list there is a link to an outline page for each book, from which is it possible to change the titles of sections, or to change their weight, thus reordering sections. From this administrative interface, it is also possible to determine whether there are any orphan pages - pages that have become disconnected from the rest of the book structure.', array('@admin-node-book' => url('admin/content/book'))) .'

                '; $output .= '

                '. t('For more information please read the configuration and customization handbook Book page.', array('@book' => 'http://drupal.org/handbook/modules/book/')) .'

                '; return $output; case 'admin/content/book': return '

                '. t('The book module offers a means to organize content, authored by many users, in an online manual, outline or FAQ.') .'

                '; case 'admin/content/book/orphan': return '

                '. t('Pages in a book are like a tree. As pages are edited, reorganized and removed, child pages might be left with no link to the rest of the book. Such pages are referred to as "orphan pages". On this page, administrators can review their books for orphans and reattach those pages as desired.') .'

                '; } if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == 'outline') { return '

                '. t('The outline feature allows you to include posts in the book hierarchy.', array('@book' => url('book'))) .'

                '; } } loki_website/modules/book/book.install0000644000004100000410000000211710475761730020525 0ustar www-datawww-data $t('Drupal'), 'value' => VERSION, 'severity' => REQUIREMENT_INFO, 'weight' => -10, ); } // Web server information. $software = $_SERVER['SERVER_SOFTWARE']; $requirements['webserver'] = array( 'title' => $t('Web server'), 'value' => $software, ); // Test PHP version $requirements['php'] = array( 'title' => $t('PHP'), 'value' => ($phase == 'runtime') ? l(phpversion(), 'admin/logs/status/php') : phpversion(), ); if (version_compare(phpversion(), DRUPAL_MINIMUM_PHP) < 0) { $requirements['php']['description'] = $t('Your PHP installation is too old. Drupal requires at least PHP %version.', array('%version' => DRUPAL_MINIMUM_PHP)); $requirements['php']['severity'] = REQUIREMENT_ERROR; } if (ini_get('register_globals')) { $requirements['php']['description'] = $t('register_globals is enabled. Drupal requires this configuration directive to be disabled. Your site may not be secure when register_globals is enabled. The PHP manual has instructions for how to change configuration settings.'); $requirements['php']['severity'] = REQUIREMENT_ERROR; } // Test DB version global $db_type; if (function_exists('db_status_report')) { $requirements += db_status_report($phase); } // Test settings.php file writability if ($phase == 'runtime') { if (!drupal_verify_install_file(conf_path() .'/settings.php', FILE_EXIST|FILE_READABLE|FILE_NOT_WRITABLE)) { $requirements['settings.php'] = array( 'value' => $t('Not protected'), 'severity' => REQUIREMENT_ERROR, 'description' => $t('The file %file is not protected from modifications and poses a security risk. You must change the file\'s permissions to be non-writable.', array('%file' => conf_path() .'/settings.php')), ); } else { $requirements['settings.php'] = array( 'value' => $t('Protected'), ); } $requirements['settings.php']['title'] = $t('Configuration file'); } // Report cron status if ($phase == 'runtime') { $cron_last = variable_get('cron_last', NULL); if (is_numeric($cron_last)) { $requirements['cron']['value'] = $t('Last run !time ago', array('!time' => format_interval(time() - $cron_last))); } else { $requirements['cron'] = array( 'description' => $t('Cron has not run. It appears cron jobs have not been setup on your system. Please check the help pages for configuring cron jobs.', array('@url' => 'http://drupal.org/cron')), 'severity' => REQUIREMENT_ERROR, 'value' => $t('Never run'), ); } $requirements['cron']['description'] .= ' '. t('You can run cron manually.', array('@cron' => url('admin/logs/status/run-cron'))); $requirements['cron']['title'] = $t('Cron maintenance tasks'); } // Test files directory if ($phase == 'runtime') { $directory = file_directory_path(); $is_writable = is_writable($directory); $is_directory = is_dir($directory); if (!$is_writable || !$is_directory) { if (!$is_directory) { $error = $t('The directory %directory does not exist.', array('%directory' => $directory)); } else { $error = $t('The directory %directory is not writable.', array('%directory' => $directory)); } $requirements['file system'] = array( 'value' => $t('Not writable'), 'severity' => REQUIREMENT_ERROR, 'description' => $error .' '. $t('You may need to set the correct directory at the file system settings page or change the current directory\'s permissions so that it is writable.', array('@admin-file-system' => url('admin/settings/file-system'))), ); } else { if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC) { $requirements['file system'] = array( 'value' => $t('Writable (public download method)'), ); } else { $requirements['file system'] = array( 'value' => $t('Writable (private download method)'), ); } } $requirements['file system']['title'] = $t('File system'); } // See if updates are available in update.php. if ($phase == 'runtime') { $requirements['update'] = array( 'title' => $t('Database schema'), 'severity' => REQUIREMENT_OK, 'value' => $t('Up to date'), ); // Check installed modules. foreach (module_list() as $module) { $updates = drupal_get_schema_versions($module); if ($updates !== FALSE) { $default = drupal_get_installed_schema_version($module); if (max($updates) > $default) { $requirements['update']['severity'] = REQUIREMENT_ERROR; $requirements['update']['value'] = $t('Out of date'); $requirements['update']['description'] = $t('Some modules have database schema updates to install. You should run the database update script immediately.', array('@update' => base_path() .'update.php')); break; } } } } // Test Unicode library include_once './includes/unicode.inc'; $requirements = array_merge($requirements, unicode_requirements()); return $requirements; } /** * Implementation of hook_install(). */ function system_install() { switch ($GLOBALS['db_type']) { case 'mysql': case 'mysqli': db_query("CREATE TABLE {access} ( aid int NOT NULL auto_increment, mask varchar(255) NOT NULL default '', type varchar(255) NOT NULL default '', status tinyint NOT NULL default '0', PRIMARY KEY (aid) ) /*!40100 DEFAULT CHARACTER SET UTF8 */ "); db_query("CREATE TABLE {authmap} ( aid int unsigned NOT NULL auto_increment, uid int NOT NULL default '0', authname varchar(128) NOT NULL default '', module varchar(128) NOT NULL default '', PRIMARY KEY (aid), UNIQUE KEY authname (authname) ) /*!40100 DEFAULT CHARACTER SET UTF8 */ "); db_query("CREATE TABLE {blocks} ( module varchar(64) DEFAULT '' NOT NULL, delta varchar(32) NOT NULL default '0', theme varchar(255) NOT NULL default '', status tinyint DEFAULT '0' NOT NULL, weight tinyint DEFAULT '0' NOT NULL, region varchar(64) DEFAULT 'left' NOT NULL, custom tinyint DEFAULT '0' NOT NULL, throttle tinyint DEFAULT '0' NOT NULL, visibility tinyint DEFAULT '0' NOT NULL, pages text NOT NULL, title varchar(64) DEFAULT '' NOT NULL ) /*!40100 DEFAULT CHARACTER SET UTF8 */ "); db_query("CREATE TABLE {boxes} ( bid int NOT NULL auto_increment, body longtext, inf