mysite.module

README v 1.0
=========
Author: agentrickard
Drupal version: 4.7.3

CONTENTS
=======

1.    MySite Overview
1.1     Installation
1.2     Features
1.3     Permissions
1.4     Menu Items
1.5     Suggested Usage
1.6     User-Submitted Content

2.    Module Overview
2.1     Core MySite Functions
2.1.1    Drupal hooks
2.1.2    Page View functions
2.1.3    Form functions
2.1.4    Callback functions
2.1.5    Helper functions
2.1.6    AJAX functions
2.1.7    Load/Include functions
2.1.8    Theme functions
2.2     Data Handling
2.3     MySite Cache
2.4     Plug-In API Overview
2.4.1     mysite_type_{name}
2.4.2     mysite_type_{name}_title
2.4.3     mysite_type_{name}_options
2.4.4     mysite_type_{name}_block
2.4.4.1  mysite_type_{name}_block_node
2.4.5     mysite_type_{name}_clear
2.4.6     mysite_type_{name}_update
2.4.7     mysite_type_{name}_settings
2.4.8     mysite_type_{name}_data
2.4.9     mysite_type_{name}_search
2.4.10     mysite_type_{name}_search_submit
2.4.11     mysite_type_{name}_autocomplete
2.4.12   mysite_type_{name}_form
2.4.13   mysite_theme_{format}
2.4.14   theme_mysite_{layout}_layout
2.4.15   theme_mysite_{format}_item

3.    Extending MySite
3.1     'Type' Plug-Ins
3.1.1     aggregator.inc
3.1.2     blog.inc
3.1.3     book.inc
3.1.4     feed.inc
3.1.5     forum.inc
3.1.6     term.inc
3.2     'Layout' Plug-Ins
3.2.1     default.php
3.2.2     columns.php
3.3     'Format' Plug-Ins
3.3.1     default.theme
3.3.2     teasers.theme
3.4     'Style' Plug-Ins
3.4.1     css ids and classes
3.4.2     default.css
3.4.3     blue.css
3.4.4     red.css

4.    Contributing
4.1     Submitting Bug Reports
4.2     Submitting Feature Requests
4.3     Submitting New Plug-Ins

5.    To-Do List
5.1     Planned Features
5.2     Calls for Help

=============

/***
  1. MySite Overview

MySite pages are designed to let users create a personalized summary of the site.  As such, the MySite module duplicates the functionality of tools like MyYahoo! and Google's personalized homepage.

The module allows registered site users to create a MySite page that contains content from throughout the site.  For sites that use the Aggregator module, users may also add feeds from external web sites to their MySite pages.

MySite was written using an API/Plug-In model that allows the core module to be extended to handle additional content types.  At initial release, the following types of content are supported by MySite:

  - Individual RSS feeds handled by Aggregator
  - RSS feed categories handled by Aggregator
  - Blog posts by individual site users
  - Forum topics and content
  - Content assigned to taxonomy terms

The intent is for the core MySite module to handle core Drupal publishing frameworks.  

In order for MySite to function correctly, you must first enable and configure the content types that your site will use.  For details and restrictions, see section 3.1 of this manual, 'Type' Plug-Ins.

/***
  1.1 Installation
  
See the INSTALL.txt file.  


/***
  1.2 Features

MySite allows each registered site user to create a page that shows a custom view of the site's content.  

Users with the permission to create a MySite page have the following features:

  - The user page exists at http://example.com/mysite/UID/view, where UID is the user's Drupal id.
  - User pages can be named and may be made public or private at the user's   discretion.  
  - An overview page, http://example.com/mysite/all, shows all public MySite pages, so that users can see how other people organize site content.
  - Users may add and remove content from their MySite.  
  - Users may sort the order of their MySite content.
  -- Users may rename the title of their MySite content blocks.
  - Users may select the following options based on the plug-ins available:
      - Layout, which controls the page structure of the MySite content.
      - Style, which controls the CSS file used to present the MySite content.
      - Format, which controls what information to display for each item of MySite content.
  
Administrators have the following settings options available:

  - How to handle the URL http://example.com/mysite.  The two options are to go to the user list (http://example.com/mysite/all) or to automatically redirect the user to his or her MySite page. 
  - Which of the avialable content plug-ins are available for users.  As noted above, MySite ships with five (5) content types; admins do not have to enable all the content types for their site.
  - How many items a user can assign to the MySite page.  Since retrieving the data for a MySite page can be intensive, there is a default cap of 10 content sections per MySite page.  This number can be altered under the module settings.
  - How many elements are displayed for each content item in a MySite page view.  As above, if we pulled back all content related to a taxonomy term, pagination and response time may become an issue.  Since MySite is designed to be a gateway to content, the default caps the element count at the 5 most recent articles.
  - Whether or not to use the included GPL icons for toolbars in MySite.
  - Whether and how long to cache MySite content views (see section 2.5 for more detail).
  - How to handle user-submitted Aggregator content (RSS feeds).  Since we allow users to submit their own RSS feeds, the administrator should set default values for the updating and categorization of these feeds (see section 3.1.1 for more detail).
  - Which taxonomy vocabularies to allow users to access with MySite.
  - A default user MySite page for use as a site homepage or navigation element.

/***
  1.3 Permissions
 
MySite has the following access control permissions by default:

  - 'administer mysite'
    Allows a user to control the MySite settings at http://example.com/administer/settings/mysite.

  - 'edit mysite'
     Grants a user the ability to create and update a MySite page.

  - 'view all mysites'
    Grants a user the ability to see public MySite pages created by other users.
  
If the Aggregator module is present and enabled, then an additional permission exists:

  - 'add mysite feeds'
    Grants users the ability to add RSS feeds to the site based on the settings established under the MySite settings page.  (See section 3.1.1 for more detail).
  

/***
  1.4 Menu Items
   
For most users, the menu paths enabled by MySite are the following:

  - http://example.com/mysite/all
    A list of all public MySite pages, visible to those with 'view all mysites' permissions.

  - http://example.com/mysite/default
    The default user's MySite page.  This is used if the site admin wants to keep a public page, especially as a front page.
    This value is set in the module settings and is optional.
     
  - http://example.com/mysite/UID/view
    The MySite page for a given user id (UID).
    
  - http://example.com/mysite/UID/edit
    The MySite configuration page for a given user.
    
  - http://exmple.com/mysite/UID/content
    The content adding page for a given user's MySite
    
All other menu items are callback functions or administrative settings.

In the main site navigation menu, two links are registered by default:

  - 'my SITENAME'
    A link to http://example.com/mysite/UID/view, where SITENAME is taken from admin/settings.
    
  - 'view all custom sites'
    A link to http://example.com/mysite/all
    

/***
  1.5 Suggested Usage
  
MySite gives your users a personal homepage for your web site.  The recommended usage is to configure the third-level domain http://my.example.com to send visitors directly to http://example.com/mysite.  

Under this configuration, set the MySite settings for 'mysite behavior' to 'Go to User mysite.'  This is the default setting and will automatically direct users from http://example.com/mysite to http://example.com/mysite/UID/view.

If MySite is configured in this manner, you can market and promote the URL http://my.example.com as you see fit.

Giving instructions for configuring subdomains on your server is beyond the scope of this file.  For more information, start with http://en.wikipedia.org/wiki/Subdomains.


/***
  1.6 User-Submitted Content
  
A note here on how MySite interacts with Aggregator.  The key feature of sites like MyYahoo! and Google's personal homepage is that they allow users to import content from across the web to a single page.

Normally, the ability to add an Aggregator feed to a Drupal site is restricted to trusted users (the site administrator, and perhaps a specific role).  In few cases are 'authenticated users' allowed to add their own RSS feeds using Aggregator. 

That is because misuse of Aggregator can have some adverse effects, particularly if feeds are updated too often, or if too many feeds are updated at the same time.

Futher, Aggregator's method for selecting Categories for RSS feeds is rarely opened to authenticated users.

MySite handles these issues in two ways.

  1) It is possible to simply turn off the ability for users to add their own RSS feeds to their MySite page.  Deselect the 'add mysite feeds' permission in your site's access control settings.
  
  2) The site administrator can configure the default settings for handling user-submitted RSS feeds.  Those controls are at http://example.com/administer/settings/mysite, under the 'Aggregator Settings' section.
  
If you grant users the ability to add RSS feeds to their MySite, they only have limited permissions, described below.

  - Users can only enter the Name and URL of the feed source.  Other settings (update frequency, categories) are set by the administrator.
  
  - When a user enters a new feed, both the title and the URL are checked against existing feeds.  If a match is found, that existing feed is used and the new feed is ignored.
  
  - Users can only assign a feed to a category if the administrator enables more than one feed category under the module settings.  In practice, we recommend that you establish a User Feeds category in Aggregator and force all user submitted feeds into this category.  Doing so has the effect of separating user-submitted feeds from those entered by site administrators.  Some, but not all, sites will desire this type of separation to indicate which feeds are 'endorsed' by the site and which are the responsibility of site users.
  
  - If only one category is available for users to add feeds, that category will be used for all user-submitted feeds.  If more than one is available, users may choose, though the administrator can set a default category.
  
Further separation of user feeds from 'site administrator' feeds is not possible without changes to the Aggregator module.  Such changes are also, in most cases, not necessary and are considered out of scope for the MySite project.  


/***
  2. Module Overview
  
This section explores how the core MySite module was written and how it interacts with plug-ins.  

/***
  2.1 Core MySite Functions

The functions in MySite are roughly grouped into 8 categories:
  
  - Drupal hooks
    Functions that interact with the Drupal core.
  
  - Page View functions
    Functions that produce themed pages within Drupal's menu/page system.
    
  - Form functions
    Functions that follow Drupal's Forms API.
    
  - Callback functions
    Functions references by menu callbacks and internal function callbacks.
    
  - Helper functions
    Standard functions that work as an API handler for MySite plugins.
    
  - AJAX functions
    AJAX handlers.
    
  - Load/Include functions
    Functions that tell MySite which plug-ins are available and when to load. them into memory.
    
  - Theme functions
    Function using Drupal's theming.

/****
  2.1.1 Drupal hooks
  
Drupal hooks are covered in the API documentation at http://api.drupal.org.

MySite uses the following Drupal hooks:

  - mysite_help()
  - mysite_menu()
  - mysite_perm()
  - mysite_user() -- attaches a link to MySite to a user's page
  - mysite_block() -- creates a MySite utility block
  - mysite_configure() -- no longer a core hook, but included here for reference.
  - mysite_cron() -- the cron hook checks MySite user content to be sure that the data sets are still available.  It is possible, for example, that a user added the taxonomy term 'dogs' to his MySite page.  If the term 'dogs' is deleted, this reference is obsolete.  The mysite_cron() hook checks for the validity of user content, deleting references to content that no longer exists.  If a content is deleted, the user will be given a message the next time he views his MySite page.  See the documentation of 'Type' plug-ins for more details.

/****
  2.1.2 Page View functions
  
These functions produce fully-themed Drupal pages.

  - mysite_view()
  This function is the default menu callback, and directs users to either the list of all public pages, or to an individual mysite page.  This function also handles page generation for http://example.com/mysite/all and the rules for handling http://example.com/mysite/default.
  
  - mysite_page($uid)
    Produces a MySite page view for the user specified by $uid.
   
    For those interested in improving MySite, pay attention to the section after the comment " /* get the data for this user  ".  It is this routine that invokes the layout and format plugins to generate MySite data views.
    
    Calls to the plug-ins exists in two lines:
    
      $data[$i]['output'] = module_invoke('mysite_type', $item->type . '_data', $item->type_id);
              
    This line perpares the data (title, author, teaser) for each content item.
    
      print theme('mysite_layout', $content);    
  
    This line uses the currently active MySite Layout file to present the content to the end user.
        
  - mysite_edit($uid)
    Creates the page where users edit the overall settings for their MySite pages (name, layout, format, style) and the privacy settings for their MySite page.
    This function invokes the Forms API and uses the functions:
      - mysite_form_edit($edit)
      - mysite_form_edit_submit($form_id, $form_values)
          
  - mysite_content($uid)
    Creates the page for adding, ordering and removing content elements from a user's MySite page.
     This function calls mysite_form_content($owner, $edit), which invokes the plug-in API to find content that may be added to a user mysite.
     
/***
  2.1.3 Form functions
  
The following functions are invoked using the Drupal Forms API:

  - mysite_form_edit($edit)
  - mysite_form_edit_submit($form_id, $form_values)
  - mysite_edit_title_validate($form_id, $form_values)
  - mysite_edit_title_submit($form_id, $form_values)  
  - theme_mysite_edit_title($form)  

The following functions may be converted to using the Forms API at a later date, so they are included here.  They currently exist as menu callbacks, which turned out to be easier to code.

  - mysite_form_content($owner, $edit)
  Where $owner is the $user object of the MySite page that content is being added to and $edit is the current content for that page.
  This function checks for all the content that is available to MySite by loading the Type includes specified by the module settings.  THe function invokes the mysite_type hook to load settings for each content type. It then invokes mysite_get_content_element() to load specific data
  
  - mysite_get_content_element($owner, $edit, $label, $options)
    This function generates the content addition form, including search options, for each content type available.  
    
/***
  2.1.4 Callback functions
  
The callback functions are either helpers for other functions or menu callbacks that are triggered by user actions (such as adding content to a MySite page).

  - mysyite_content_add($uid = NULL, $type = NULL, $type_id = NULL, $title = NULL)
    The call to add content to a MySite page.  The NULL values are there to force error checking to guard against spoofing.
    
  - mysite_content_remove($uid = NULL, $mid = NULL)
    The call to remove content from a MySite page, where $mid is the idintifier of the content element.
    
  - mysite_content_promote($uid = NULL, $mid = NULL, $up = TRUE)
    A single function to handle both promotion and demotion of elements (sorting).  Default is $up (promote).
    
  - mysite_updated($uid = NULL)
    A generic function called by other functions in order to record an update timestamp to a MySite page.
    
  - mysite_add_link($uid, $type, $type_id)
    A theme-assist function that generates a Drupal link to invoke mysite_content_add.

  - mysite_remove_link($uid, $mid)
    A theme-assist function that generates a Drupal link to invoke mysite_content_remove.
    
  - mysite_promote_link($uid, $mid)
    A theme-assist function that generates a Drupal link to invoke mysite_content_promote where $up = TRUE.    
    
  - mysite_demote_link($uid, $mid)
    A theme-assist function that generates a Drupal link to invoke mysite_content_promote where $up = FALSE.    
    
  - mysite_check($uid, $type, $type_id, $mid = NULL)
    A data check to see if a given piece of content already exists on a user's MySite page.  Prevents duplicate entries.
    
/***
  2.1.5 Handler functions
  
The handler functions are used to enable the include files to offload some of the logic to the main module.  

  - mysite_search_handler($data, $type)
    This function is part of the API, and allows the Type includes to hand content searches to a single handler.  Data comes from mysite_type_{name}_search_submit().
    
  - mysite_block_handler($data)
    Data handling for block placement. This function is part of the API. Called by the mysite_type_{name}_block() functions, this allows all block links to be formatted by a single function.
    
  - mysite_sort_options($sort)    
  Helper functions to sort options array by weight.
  
  - mysite_usort_options($a, $b)
  PHP usort function invoked by mysite_sort_options().
  
  - mysite_message($uid)
  Displays MySite system messages to the user.  Invoked by any of the Page View functions.  After displaying the message, it is cleared from the database.
  
  - mysite_get_myid($type = NULL, $string = NULL, $title = NULL, $content = NULL)
  Return a valid numeric id for content that uses strings for keys.  If the id does not exist, one will be created and stored in the mysite_content table.
  
  - mysite_get_custom($type, $type_id, $all = FALSE) 
  Returns content associated with custom type includes.  This is the data for items that are not stored with regular Drupal ids.  See refine.inc for example.
  
/***
  2.1.6 AJAX functions
  
The intention is to add additional AJAX interface elements to MySite, particularly drag-and-drop sorting.  At inital release, only an AJAX autocomplete function is functional.

For more detail on AJAX in Drupal, see http://drupal.org/node/42552

  - mysite_autocomplete($string, $type = NULL)
  Allows registration of autocomplete functions through the include files. Since AJAX autocomplete requires a menu callback, and the include files  do not have menu functions, we filter all AJAX requests through a single function.
See also the section on mysite_type_{name}_autocomplete().

/***
  2.1.7 Load / Include Functions
  
To reduce code overhead, the MySite module only loads the plug-ins that it needs when it's menu callbacks are invoked.  These functions handle the registration and loading of those plug-ins.

  - mysite_get_includes($type = NULL, $name = NULL) 
    Returns the valid include files for a given include type.  A single file can be specified by passing $name (the filename without extenstion). Results are returned according to file_scan_directory() with key of 'name'. See http://api.drupal.org/api/4.7/function/file_scan_directory 
    
  - mysite_load_includes($type = NULL, $name = NULL, $load_all = FALSE)  
    Loads the desired includes for this action.  Uses include_once() to load the necessary files.  The function also returns a simple array of include files that were loaded.
    
  - mysite_load($mysite)
    Loads the associated files for a specific user's MyPage view (stylesheets, mysite includes).
    
  - mysite_get_mask($type = NULL) 
    Gets file extension masks for each type, for use by file_scan_directory().
    
  - mysite_get($uid = NULL) 
    Finds the settings for a specific user's MySite. Returns an object containing the details of a user's mysite page -- but not the content of the page.
    
/***
  2.1.8 Theme functions
  
A variety of theme functions for MySite, including user help.  These themes are mostly placeholders.

  - theme_mysite_create_help($owner)
    Help for users creating their MySite page.
    
  - theme_mysite_content_help($owner)
    Help for users adding content to their MySite page.
    
  -  theme_mysite_block_help()
    Help for users placed in the MySite block.
    
  - theme_mysite_anonymous_help()
    Help message for anonymous users.
    
  - theme_mysite_actions($uid, $mid)
    Places the promote/demote/remove links next to a content element.  Supports GPL icons included in the module package.
    
/***
  2.2 Data Handling
  
Because of the use of the Aggregator module, not all MySite content consists of node content.  As a result, we can't simply use node_load() and node_view() to present MySite content.  Further, we want users to be able to customize the presentation of MySite content, as provided by the Layout, Format, and Style plug-in handlers.  

The following describes the process by which content is loaded onto a MySite page.

  - User requests a MySite page, referenced by UID.
  - The data settings (title, layout, format, stylesheet, and data elements) for that page are returned.
  - The Layout for a user's page is loaded, and each data element is processed.
  - Each content item in a page element is run through the Format functions and placed in the layout.
  - The page view is generated and the stylesheet loaded.
 
The format for generating data to be used by MySite is described under mysite_type_{name}_data.
    
/***
  2.3 MySite Cache

Since MySite lets users load content from various sources onto one page, it is possible that the query count and load time for the page may get quite large.  To mitigate this effect, administrators are giving the option of caching the content for a MySite page view.

Under http://example.com/administer/settings/mysite, simply set the MySite cache to whatever interval you think is appropriate.  Default is off. 

When the MySite cache is active, the content portion of a user's MySite will be stored in the Drupal cache for the specified period of time.  If the user edits his or her MySite page, the cache is cleared and the page reset.
    
/***
  2.4 Plug-In API Overview

MySite is extensible, and new plug-ins are encouraged.  This section documents each of the functions used within the MySite plug-in system.  

Plugins site within the mysite module directory in a directory named 'plugins.'  There are five directories inside the 'plugins' directory.

  - formats
  - icons
  - layouts
  - styles
  - types

For more information on plugins, see section 3, Extending MySite.  

For this documentation, we will look at 'term.inc' for 'Type' includes and at default.php and default.theme for Layout and Format includes.

/***
  2.4.1     mysite_type_{name}

This function registers the Type include with the core MySite module.  On success, it returns an array of values.  Optionally, it also returns the content selection options for the type.

      --code--
      function mysite_type_term($get_options = TRUE) {
 A)     if(module_exist('taxonomy')) {
 B)       $type = array(
            'name' => t('categories'),
            'description' => t('<b>Categories</b>: All nodes for a specific taxonomy term'),
            'include' => 'term',
            'prefix' => t(''),
            'suffix' => t('articles'),
            'weight' => -3,
            'form' => FALSE,
            'label' => t('Add Tagged Content'),
            'help' => t('You can track content by keyword category. Type a keyword in the search box to find the matching category, or choose one of the example links.'),
            'search' => TRUE,
            'threshold' => TERM_THRESHOLD
          );
C)      if ($get_options) {
            $type['options'] = mysite_type_term_options();
          }
D)          return $type;
        }
      }
      -- code --      


Notes:

  A) The type is dependent on an external module.  We must check for it.  This will be true for all MySite Type plug-ins.
  
  B) Definition of the $type array.  The elements are all required.  Their usage is as follows:
            'name' => The human readable name of this content type
            'description' => Description of the type, used on the module settings page
            'include' => The name of the include file, without file extension.
            'prefix' => A prefix to attach to any content titles (using mysite_type_{name}_title).
            'suffix' => A prefix to attach to any content titles (using mysite_type_{name}_title).
            'weight' => The weight of this type for presentation to the user.
            'form' => A boolean value that indicates the presence of mysite_type_{name}_form
            'label' => The title shown to a user on the MySite content addition page.
            'help' => Help text for adding content of this type.
            'search' => A boolean indicating whether this element is searchable.
            'threshold' => An integer indicating how many items to show in the content table for this type.

  C) $get_options is a boolean value used internally by the MySite module.

  D) The return value is the defined array $type.
  

/***
  2.4.2     mysite_type_{name}_title
  
Produces a generic title for a piece of content.

      -- code --
A)   function mysite_type_term_title($type_id = NULL, $title = NULL) {
B)     if(!empty($type_id)) {
C)       if(is_null($title)) {
            $term = taxonomy_get_term($type_id);
            $title = $term->name;
          }  
D)      $type = mysite_type_term();
          $title = $type['prefix'] . ' ' . $title . ' ' . $type['suffix'];
          $title = trim(rtrim($title));
          return $title;
        }
E)     drupal_set_message(t('Could not find title'), 'error');
        return;
      }  
      -- code --

Notes:

  A) $type_id indicates the term TID to pull data for.  For other data types, the unique identifier will be used.
  
  B) If the $type_id is blank, this function fails and returns an error E).
  
  C) In some cases, $title may be set; if it is not, we get the default title for the content type.
  
  D) Grab the prefix and suffix values for the title from mysite_type_{name}.  Then process the title and return it.
  
  E) Error on failure.


/***
  2.4.3     mysite_type_{name}_options
  
The options function returns values for presentation to the user when selecting content.  If the array returns empty, this content selection will not appear under the user options page.

    -- code --
    function mysite_type_term_options() {
A)  $options = array();
  
B)  $vocabularies = taxonomy_get_vocabularies();
        // eliminate forums from the generic term view
        $forum = variable_get('forum_nav_vocabulary', 0);  
        if (is_array($vocabularies) && !empty($vocabularies)) {
          foreach ($vocabularies as $vocabulary) {
            if($vocabulary->vid != $forum) {
              $terms[$vocabulary->name] = taxonomy_get_tree($vocabulary->vid);
            }  
          }
        }
        if (is_array($terms) && !empty($terms)) {  
          $i =0;
C)       foreach ($terms as $key => $value) {
            foreach($value as $term) {
D)          if ($i < TERM_THRESHOLD) {
               $depth = str_repeat('-', $term->depth) . ' ';
E)            $options['group'][] = $key;
               $options['name'][] = $depth . $term->name;
               $options['type_id'][] = $term->tid;
               $options['type'][] = 'term';
               $i++;
             }  
           }
         }  
       }
F)    return $options;
     }
      -- code --
      

Notes:

  A) Initialize the $options array.
  
  B) Logic for getting the different content options.  In this case, we retrieve the names of all taxonomy terms that are not the site's forum.

  C) Iterate through the data for $terms.  This is not necesary in all cases.
  
  D) The THRESHOLD value indicates how many items to put in the table, generally 10.  Iterate through the list of options until the threshold is reached.
  
  E) It's easier to use a flat array in the module, so we count the $depth of each item and prepare the $options array:

              $options['group'][] => The overall grouping of the items.
              $options['name'][] => The name of this option, as will be presented to the user.
              $options['type_id'][] =>  The unique identifier for this item.
              $options['type'][] => The type of the item.  Identical to the plug-in {name}.

  F) Return the value.              

/***
  2.4.4     mysite_type_{name}_block
  
This function places appropriate links to add/remove content from a MySite page within the MySite block.  Each content type checks the requested URL for paths that are applicable to that type.  See the example and notes below.

        -- code --
A)     function mysite_type_term_block($arg, $op = 'view') {  
B)      global $user;
C)      if (user_access('access content') && $arg[0] == 'taxonomy' && $arg[1] == 'term' && is_numeric($arg[2])) {
D)        $term = taxonomy_get_term($arg[2]);
E)         $data = array();
            $data['uid'] = $user->uid;
            $data['type'] = 'term';
            $data['type_id'] = $term->tid;
            $data['title'] = mysite_type_term_title($term->tid, $term->name);
F)         $content = mysite_block_handler($data);
G)         return $content;
          }  
        }  
        -- code --

Notes:          

  A) Here, $arg is an array containing the argcz through arg(5).  This is set by mysite_block() so we don't have to invoke arg() multiple times.  $op is defined in hook_block and currently only applies to case 'view.'
  
  B) Invoke the $user object to check permissions.
  
  C) The logic for the URL callback.  Here, we are chcking for the format http://example.com/taxonomy/term/TID and ensuring that TID is a number.  Other content types will use different IF logic here.  
       We also check the appropriate access control for this content type.  If the user can't see the content, there is no point in adding a link to it.
  
  D) Get the data associated with this term.  Other content types will use different methods; this call is here so that we can populate teh $data array correctly.
  
  E) Populate the $data array, using the following format:
  
            $data['uid'] => The user id of the user looking at this page.
            $data['type'] => The type of content, usually the same name as the plug-in file.
            $data['type_id'] => The unique type id for this content type (here, the TID).
            $data['title'] => The generic title for this content item, as defined my mysite_type_{name}_title($type_id, $name).

  F) Pass the $data array to the block handler function.  This puts the display logic in the core module, not the plug-in.
  
  G) Return the $content of the block.
  
Parts F) and G) will be the same for all Type includes.

/***
  2.4.4.1     mysite_type_{name}_block_node
  
This function was added (2-NOV-2006) to address an error in the original forum.inc file.  In order to check to see if an individual node is part of a MySite data collection, we need to run specific queries depending on node type.

The inclusion of this function is called by mysite_block() in the event that the requested page is a node.  Here is the relevant code from mysite_block():

      -- code --
      // universal handler for nodes
      if ($arg[0] == 'node' && is_numeric($arg[1])) {
        $query = db_fetch_object(db_query("SELECT n.type, n.nid FROM {node} n WHERE n.nid = %d", $arg[1]));
        if ($query->nid > 0) {
          $content .= module_invoke('mysite_type', $query->type . '_block_node', $query->nid);
        }  
      }
      -- code --
      
If a type include wants to add a block element to add/remove content from a node page, the include file must define the function mysite_type_{name}_block_node.  This function is not required, and is currently only implemented in the forum.inc and blog.inc files.  It wil also be used in book.inc.

Here is sample code taken from blog.inc:

      -- code --
      function mysite_type_blog_block_node($nid = NULL) {
A)     if (!is_null($nid)) {
B)       global $user;
C)       $sql = "SELECT DISTINCT(u.uid), u.name FROM {users} u INNER JOIN {node} n ON u.uid = n.uid WHERE n.nid = %d AND n.status = 1 AND n.type = 'blog'";
D)       $blog = db_fetch_object(db_query($sql, $nid));
          $data = array();
          $data['uid'] = $user->uid;
          $data['type'] = 'blog';
          $data['type_id'] = $blog->uid;
          $data['title'] = mysite_type_blog_title($blog->uid, $blog->name);
E)       $content = mysite_block_handler($data);
F)       return $content;
        }  
      }  
      -- code --

Notes:
  
  A) The mysite_block function should pass a valid $node->nid value here, but we check it, just in case.

  B) The user looking at the page is the owner of this mysite content.

  C) Get the relevant data for identifying this content type.

  D) Cast the data into an array as defined by mysite_type_{name}_block() (see 2.4.4).
  
  E) Set the content using mysite_block_handler.

  F) Return the content of the block.
    
/***
  2.4.5     mysite_type_{name}_clear
  
This function is invoked by the MySite cron hook.  Its purpose is to remove deleted content from a user's MyPage.  

        -- code -- 
        function mysite_type_term_clear($type) {
          // fetch all the active records of this type and see if they really exist in the proper table
A)       $sql = "SELECT mid, uid, type_id, title FROM {mysite_data} WHERE type = '%s'";
          $result = db_query($sql, $type);
          $data = array();
B)       while ($item = db_fetch_array($result)) {
            $sql = "SELECT tid FROM {term_data} WHERE tid = %d";
            $check = db_fetch_object(db_query($sql, $item['type_id']));
C)         if(empty($check->tid)) {
              $data[$item['mid']]  = $item;
            }  
          }
D)       return $data;
        }
        -- code --

Notes:
  
  A) Select all the data associated with this content type.
  
  B) Check that the type_id is still valid for its parent table, in this case the {term_data} table.
  
  C) If no result is returned, we add this item to the array for deletion. 

  D) Return the $data array for processing by mysite_cron().


/***
  2.4.6     mysite_type_{name}_update

This callback function is a hook that allows type plugins to act on a user-submitted change to a MySite page.  There are currently no implementation of this hook.
The function takes as its argument the user id $uid of the MySite page being changed.

Possible uses for this function include the creation of a path.in that will function like the pathauto.module.


/***
  2.4.7     mysite_type_{name}_settings 
  
The settings hook enables type files to establish specific settings for interacting with modules.  This is an optional function.
In the core release, only two types use this function, term.inc and feed.inc.  These settings control which vocabularies usrs may select from and how to handle user-submitted feeds, respectively.
The mysite_type_{name}_settings function uses the FormsAPI, and Drupal's 'system_settings_form' function. (See http://api.drupal.org/api/4.7/function/system_settings_form)
The code for feed.inc is lengthy, so we'll look as term.inc for this example.

      -- code --
      function mysite_type_term_settings() {
A)     $vocabularies = taxonomy_get_vocabularies();
        // eliminate forums from the generic term view
        $forum = variable_get('forum_nav_vocabulary', 0);  
        unset($vocabularies[$forum]);
        if (is_array($vocabularies)) {
          foreach($vocabularies as $vocabulary) {
            $options[$vocabulary->vid] = $vocabulary->name;
          }
B)         $form['mysite_term']['mysite_term_vocabularies'] = array(
                '#type' => 'checkboxes', '#title' => t('Allowed Vocabularies'), '#default_value' => variable_get('mysite_term_vocabularies', NULL),
                '#options' =>  $options,
                '#required' => FALSE,
                '#description' => t('What vocabularies should be displayed to MySite users?')
              );      
C)       return system_settings_form('mysite_term', $form);          
        }
        else {
D)       return t('The taxonomy module is not active or there are no vocabularies.  These features cannot be used.');  
        }
      }
      -- code --

  A)  All this form needs to do is present checkboxes for each vocabulary.  In this section, we create the $options array to be used by the form.  Note that we throw away the forum vocabulary.

  B)  When naming the form, use the format ['mysite_{name}']['mysite_{name}_{variable}'] in order to avoid namespace collisions with other type files.
  
  C)  Invoke the form.
  
  D)  On an error, print a message to the admin.

/***
  2.4.8     mysite_type_{name}_data
  
This is the core function for each content type.  The data refers to the actual content to be presented to the end user.  It may be useful to think of this function as the MySite version of node_load().  Why not use node_load? because not all MySite content will be nodes.
When querying for nodes, be sure to wrap your query in db_rewrite_sql() to respect node_access rules.  (See http://api.drupal.org/api/4.7/function/db_rewrite_sql).

        -- code --
        function mysite_type_term_data($type_id = NULL) {
A)       if(!empty($type_id)) {
            
            $sql = db_rewrite_sql('SELECT n.nid FROM {node} n INNER JOIN {term_node} t ON n.nid = t.nid WHERE n.status = 1 AND type != "forum" AND t.tid = %d ORDER BY n.changed DESC');
B)         $result = db_query_range($sql, $type_id, 0, variable_get('mysite_elements', 5));
        
C)         $data = array(
              'base' => 'taxonomy/term/' . $type_id,
              'xml' => 'taxonomy/term/' . $type_id . '/0/feed'
              );
        
            $items = array();
            $i = 0;
            // we can't use node_view() here, because aggregator doesn't use nodes
            // and we need the same data structure for all content
D)        while ($nid = db_fetch_object($result)) {
              $node = node_load($nid->nid);
              $items[$i]['type'] = $node->type;
              $items[$i]['link'] = l($node->title, 'node/' . $nid->nid);
              $items[$i]['title'] = check_plain($node->title);
              $items[$i]['subtitle'] = NULL;                 
              $items[$i]['date'] = $node->changed;
              $items[$i]['uid'] = $node->uid;
              $items[$i]['author'] = check_plain($node->name);
              $items[$i]['teaser'] = check_markup($node->teaser, $node->format);
              $items[$i]['nid'] = $node->nid;      
              $i++;
            }
            $data['items'] = $items;
 E)        return $data;
          }
 F)      drupal_set_message(t('Could not find data'), 'error');
          return;  
        }
        -- code --

Notes:

  A) Error checking routine.  $type_id is required and should always be present.
  
  B) The SQL query required to pull back the data we want.  We wrap this in db_rewrite_sql to respect node_access controls.  In this case, nodes assigned to a taxonomy term.  This query will generally be modified from the parent module.  In this case, it is a rewrite of taxonomy_node_select().  The range is determined by the MySite setting for 'items' to present (default is 5).
  
  C) The $data array, used by both the Layout and Format includes.  First we assign these values:
  
          $data['base'] => The base link for this content.  Here a link to a taxonomy view page.  Use Drupal paths here, not path aliases.  Later we will run them through the l() function.
          $data['xml'] => An optional XML callback location for the content type.
          
  D) A positional array is created for each content item.  The following data is added (and security checked).      
  
          $item[$i]['type'] => The type of content, either $node->type or a static variable.  We include this so that Formats can treat different content types uniquely, if desired.
          $item[$i]['link'] => The Drupal link to the original item in its normal context.
          $item[$i]['title'] => The title of the item.
          $item[$i]['subtitle'] =>  Not used here, an optional subtitle for the item.        
          $item[$i]['date'] => The updated timestamp for this item.
          $item[$i]['uid'] => The user id of the item's author, if available.
          $item[$i]['author'] => The user name of the item author, if available.
          $item[$i]['teaser'] => The teaser text of the item, if available.         
          $item[$i]['nid'] => The node id of the item, if available. We include this so that Formats can treat different content types uniquely, if desired.
      
      Note that we do not include a 'body' element.  It is not within the scope of MySite to present the entire body of a content item.
      
   E) Append the $item array to the $data array and return.
   
   F) This is a debugging message, used to check for the validity of data requests in A).

/***
  2.4.9     mysite_type_{name}_search
  
This function allows users to search the content type for topics.  If this function is present, a search box is populated on the http://example.com/mysite/UID/content page.  If this function is not present, then the $type['search'] value in mysite_type_{name}() should be set to FALSE.  
When querying for nodes or taxonomies, be sure to wrap your query in db_rewrite_sql() to respect node_access rules.  (See http://api.drupal.org/api/4.7/function/db_rewrite_sql).

        -- code --
        function mysite_type_term_search($uid = NULL) {
A)       if (!is_null($uid)) {
B)         $form['add_term']['term_title'] = array('#type' => 'textfield',
              '#title' => t('Tag Name'),
              '#default_value' => $edit['term_title'],
              '#maxlength' => 64,
              '#description' => t('The tagged content you wish to add.'),
              '#required' => TRUE, 
              '#autocomplete_path' => 'autocomplete/mysite/term'
            );  
C)         $form['add_term']['uid'] = array('#type' => 'hidden', '#value' => $uid);
            $form['add_term']['type'] = array('#type' => 'hidden', '#value' => 'term');
            $form['add_term']['submit'] = array('#type' => 'submit', '#value' =>t('Add term'));  
D)         $output .= drupal_get_form('mysite_type_term_search', $form);  
E)         return $output;
          }
        }
        -- code --

Notes:

  A) Error checking to ensure that $uid is passed correctly.
  
  B) Define this form element using the Forms API.  The autocomplete function calls mysite_type_{name}_autocomplete, if applicable.
  
  C) When searching, we are only looking for the TYPE_ID, so we pass the TYPE and UID as hidden fields.
  
  D) Render the form.
  
  E) Return the rendered for to be displayed.

/***
  2.4.10     mysite_type_{name}_search_submit
  
The handler for content type searches, following the Forms API.  Data is processed and then passed to the mysite_search_handler(). The handler decides what to do based on the returned data.

        -- code --
        function mysite_type_term_search_submit($form_id, $form_values) {
          // eliminate forums from the generic term view
A)      $forum = variable_get('forum_nav_vocabulary', 0);    
          // we use LIKE here in case JavaScript autocomplete support doesn't work.
          // or in case the user doesn't autocomplete the form
         $sql = db_rewrite_sql("SELECT t.tid, t.name, t.description FROM {term_data} t WHERE t.name LIKE LOWER('%s%%') AND t.vid != '%s'", 't', 'tid');          
B)      $result = db_query($sql, $form_values['term_title'], $forum);
          $count = 0;
C)       while ($term = db_fetch_object($result)) {
D)        $data[$count]['uid'] = $form_values['uid'];
            $data[$count]['type'] = $form_values['type'];
            $data[$count]['type_id'] = $term->tid;
            $data[$count]['title'] = mysite_type_term_title($term->tid, $term->name);
            $data[$count]['description'] = $term->description;   
            $count++;
          }
          // pass the $data to the universal handler
E)       mysite_search_handler($data, 'term');
          return;
        }
        -- code --
        
Notes:

  A) In this case, forums are handled as separate content types, so we must omit them from the search results.
  
  B) The search query to match our data string.  We use LIKE to ensure maximum matches.  Our query is wrapped in db_rewrite_sql() to handle taxonomy access controls.
  
  C) Loop through the data returns and set an array for processing.  Positional, because we may have more than one match and the handler will iterate through the $data set.
  
  D) The $data array:
  
            $data[$count]['uid'] => The UID for this MySite page.  Passed in the search form.
            $data[$count]['type'] => The MySite content type. Passed in the search form.
            $data[$count]['type_id'] => The unique type_id that matches this query.
            $data[$count]['title'] => The generic title of this content item.
            $data[$count]['description'] => A short description of the item to help users select when more than one match is returned.
  
  E) Invoke the search handler and exit.  No return value is needed, because the handler function finishes the operation.
  
/***
  2.4.11     mysite_type_{name}_autocomplete

Optional AJAX function that complements mysite_type_{name}_search.  See http://drupal.org/node/42552, the Drupal handbook page on autocompletes.
When querying for nodes or taxonomies, be sure to wrap your query in db_rewrite_sql() to respect node_access rules.  (See http://api.drupal.org/api/4.7/function/db_rewrite_sql).

        -- code --
        function mysite_type_term_autocomplete($string) {
          $matches = array();
          // eliminate forums from the generic term view
          $forum = variable_get('forum_nav_vocabulary', 0);   
          $sql = db_rewrite_sql("SELECT t.tid, t.name, t.description FROM {term_data} t WHERE t.name LIKE LOWER('%s%%') AND t.vid != '%s'", 't', 'tid');          
          $result = db_query_range($sql, $string, $forum, 0, 10);
          while ($term = db_fetch_object($result)) {
            $matches[$term->name] = check_plain($term->name);
          }
          return $matches;
        }
        -- code --
        
/***
  2.4.12   mysite_type_{name}_form

An optional element that will add an additional form to the content page underneath this type heading.  For sample usage, see feed.inc, which attaches a form that allows users to submit their own RSS feeds.

Use of this function should follow the Forms API.

/***
  2.4.13   mysite_theme_{format}
  
Not a theme function, but a format registration function.  The mysite_theme_{format}  function is used by Format plug-ins to register the format on the MySite edit page (http://example.com/mysite/UID/edit).

        -- code --
        function mysite_theme_default() {
 A)      $output = t('headlines');
          $output .= t('<ul><li><a>sample headline</a></li></ul>');
          return $output;
        }
        -- code --

Notes:        

  A) The purpose is to return a section of HTML code that will preview how the Format function will display MySite content.  In the default format, we simply return an unordered list of headline links.

/***
  2.4.14   theme_mysite_layout

The theme_mysite_{layout}_layout function is a template-style theme function that controls the position of items on a MySite page.  Based on the user's settings, MySite will load the appropriate Layout file to call this theme function.
Layout functions can be edited within the plugins/layouts directory, or overridden using PHP Template's theming functions.  See http://drupal.org/node/11811 for details.

        -- code --
A)    function theme_mysite_default_layout($content) {
          // break the array into pieces
          $owner = $content['owner'];
          $mysite = $content['mysite'];
          $data = $content['data'];
          
B)      // wrap the output
          $output = '<div id="mysite">';
C)       if(user_access('access user profiles')) {
            $output .= '<div class="profile">' . l(t("%name's profile", array('%name' =>$owner->name)), 'user/' . $owner->uid) . "</div>";
          }
          
D)       // cycle through the data
          foreach ($data as $key => $value) {
            $output .= '<div class="group">';
            $output .= '<fieldset class="collapsible">';
            $output .= '<legend>&nbsp;' . $value['title']. "&nbsp;</legend>";
            if (!empty($value['output'])) {
              if (!empty($value['output']['image'])) {
                $output .= $value['output']['image'];
              }      
E)           $output .= theme('mysite_' . $value['format'] . '_item', $value['output']['items']);
              $output .= '<span class="more">' . l(t('read more'), $value['output']['base']) . '</span>';
            }
            else {
F)           $output .= t('<p>No content found.</p>');
            }
G)         $output .= ' <span class="actions">' . $value['actions'] . '</span> ';
            $output .= '</fieldset>';
            $output .= '</div>';    
          }
          $output .= '</div>';
H)      print theme('page', $output);
          return;
        }
        -- code --

Notes:

  A) This function takes the $content array as an argument.  This array is prepped and sent by the mysite_page() function.  The $content data is defined by the mysite_type_{name}_data API.
       The $content array has three parts:
       
          - $owner => The $user object of the MySite page owner.
          - $mysite => MySite content settings for the owner.
          - $data => The actual content as prepared by mysite_type_{name}_data().
  
  B) A user access check before printing a link to the MySite owner's profile.
  
  C) It may be useful to think of the $data as blocks, in a Drupal sense.  The Layout file produces a wrapper for containing the $data content and then iterates through each content element to generate the page.  Here we define a simple container for the content.

  D) Iterate through the $data array and present each content section.
  
  E) This line invokes the Format theme function for the individual content items in each page element.
  
  F) A placeholder notice in the event that no content is returned.
  
  G) The action links and buttons (promote / demote / remove).  These should normally be appended to all content groups in a Layout.
  
  H) Prints the content in the normal PHPTemplate wrapper.  Note to layout developers: it is not necessary to invoke normal Drupal theming here.  If you wanted to create a totally custom template, you could.  Experiment by changing this line to "print $output;" to see an example.
       Since the layout files are not invoked via menu callback, ew must invoke a theme('page', $output) here if we wish to wrap the content in the current theme.  (See http://drupal.org/node/22218#handling-of-return-values-from-callbacks.)
  
/***
  2.4.15   theme_mysite_{format}_item
    
Takes the data for a content group and formats each item according to the Format theme.

        -- code --
A)    function theme_mysite_default_item($item) {
          $list = array();
B)       foreach ($item as $element) {
C)        if (empty($element['subtitle'])) {
              $list[] = $element['link'];
            }
            else {
              $list[] = $element['link'] . ' ' . $element['subtitle'];
            }
          }
D)      $output = theme_item_list($list, $title = NULL, $type = 'ul');
E)      return $output;
        }
        -- code --
        
  A) Takes as an argument, the content array defined by mysite_type_{name}_data() as a positional array.

  B) Iterate through the data and process each element.
  
  C) Add a subtitle if present -- this is a personal preference and is not required.
  
  D) Theme the $list according to a standard Drupal theme function.  You can use any theme here, or create your own.  This method is simply the easiest way to product an unordered content list.
  
  E) Return the formatted HTML to the Layout function.
  

/***
  3. Extending MySite

MySite is designed to be extended to handle additional types of content or content groupings.  Hopefully the documentation can show you how to create new plug-ins to extend MySite.  This section discusses briefly what each of the current plug-ins does.

/***
  3.1 'Type' Plug-Ins

'Types' are content definitions.  They control the content selection options available to the user and generate the data that is presented on a MySite page view.

They are named {type}.inc and stored in the /mysite/plugins/types/ folder.

/***
  3.1.1  aggregator.inc

Handles content defined by an Aggregator category.  Content of this type will be a collection of all RSS feed items within a specific Aggregator category.  

It is recommended that you establish a 'User Feeds' category to handle feeds contributed by MySite users.

/***
  3.1.2 blog.inc

Handles blog content hosted on your Drupal site.  Generates a list of all active bloggers (defined as active users who have at least one published blog post).

/***
  3.1.3 book.inc

Handles book pages.  Generates a list of all books and show the most recently updated pages.  

/***
  3.1.4 feed.inc

Handles the content for an individual RSS feed (as opposed to the categories handles by aggregator.inc).  This plug-in also uses the mysite_type_{name}_form() function to allow users to add their own RSS feeds.

/***
  3.1.5 forum.inc

Handles forum content.  Content choices are grouped by form category.  Note: Forums are separated from other taxonomy terms by design.

/***
  3.1.6 term.inc

Handles content of any type (except forum posts) assigned to a taxonomy term.  Currently does not handle content roll-ups according to term hierarchies.

/***
  3.2 'Layout' Plug-Ins

These plug-ins handle the page architecture of the MySite page.  They are named {layout}.php and stored in the /mysite/plugins/layouts folder.

Layouts have two parts: a {layout}.php file that defines the layout theme and a {layout}.png image that is used in the layout selection form.

To create a new {layout}.png, use the provided default.png as a blank.

/***
  3.2.1 default.php
  
Returns all content in a single column within the standard page template for the site.  

/***
  3.2.1 columns.php
  
Returns all content in a two columns within the standard page template for the site. 

/***
  3.3 'Format' Plug-Ins

Handles the presentation theming for individual content elements on a MySite page.  They are named {format}.theme and stored in the /mysite/plugins/formats/ folder.

/***
  3.3.1 default.theme
  
Prepares each item in a content group as part of an unordered list of headlines links.  

/***
  3.3.2 teasers.theme
  
Prepares each item in a content group within its own <div> and presents the headline link and 'teaser' element.  

/***
  3.4     'Style' Plug-Ins

Handles stylesheets for user MySite pages.  Users may select the style that they prefer.  They are named {style}.css and stored in the /mysite/plugins/styles/ folder.

Current styles are very simple.  The default style is designed to respect inherited styles, and is deliberately minimal.

All stylesheets should include the core styles defined in default.css, rewritten as needed.  Only the selected stylesheet is loaded on page views.

/***
  3.4.1 css ids and classes
  
The following ids and classes are defined within blue.css and should be used as a template for additional styles.  Remember to be consistent with styles so that all stylesheets can be used with all formats and layouts.

  - #mysite
    ID definition for the MySite content, included as the container for all layouts.
  
  - #mysite .group
    Class definition for a MySite content group, used for all layouts.

  - #mysite .profile
    Class definition for a MySite profile link, used for all layouts.

  - #mysite h2
    Style definition for h2 (Drupal's default title formatting), used for all layouts

  - #mysite h3
    Style definition for h3, used for MySite subtitles.

  - #mysite a 
     Link formatting within a MySite ID.

  - #mysite .more a
    Link formatting for the 'more of this content' link placed within each content group.
  
  - #mysite .actions a
    Link formatting for the action links (promote / demote / remove) placed within each content group.
    
  - #mysite img
    Rules for handling images within the MySite id.

  - #mysite .mysite_content
    Clears any floats invoked when closing a content group.

  - #mysite-blue-edit
    Formatting for sample text to be used by mysite_form_edit() on http://example.com/mysite/UID/edit
  
  - #mysite-blue-edit a
    Formatting for links within the sample text.  
  
  - #mysite-blue-edit h2
    Formatting for titles within the sample text.  

/***
  3.4.2 default.css
  
The standard css file, it also defines the style ids and classes used by MySite.  Those are:

  - #mysite
      The id for mysite content, always used.
  
  - #mysite .group 
      The <div> style for a MySite content group.

  - #mysite-default-edit
      Formatting for sample text to be used by mysite_form_edit() on http://example.com/mysite/UID/edit
  
  - #mysite-default-edit a 
      Formatting for links within the sample text.

/***
  3.4.3 blue.css
  
A blue link style, with borders around each content group.  
  
/***
  3.4.4 red.css

A red link style, with borders around each content group.


/***
  4. Contributing

The MySite project page is at http://drupal.org/project/mysite.  You are invited to submit bug reports and feature requests there.

/***
  4.1  Submitting Bug Reports

When submitting a bug report, please include the following:

  - Drupal version (e.g. Drupal 4.7.3)
  - PHP version (e.g. PHP 5.1.1)
  - Database type and version (e.g. MySQL 4.1)
  
If you don't include this, I'll just have to ask you for it.  

/***
  4.2  Submitting Feature Requests

Feature requests are welcome, but please read section 5.1 before submitting.

/***
  4.3  Submitting New Plug-Ins
  
To submit a new plug-in for MySite, go to the project page and submit a new feature request.  File the feature request under the specific type of Plug-In that you are submitting.

/***
  5. To-Do List

This section covers known issues and a general development outline.


/***
5.1     Planned Features

The following features are supported by the existing code structure, but not implemented.

  a) Drag-and-drop sorting of content priority.
  b) AJAX-based table pagination of content lists.
  c) User control over the names of content groups.
  d) Separate format settings for each content group.
  e) User-submitted css files.


/***
5.2     Calls for Help  

  a) PostGres support.  The module installer includes a pgsql case, but this has not been tested.
  
  b) AJAX drag-and-drop sorting.  Looking for a method to sort content via drag-and-drop on both the View and Content pages.
  
