]> gitweb.fluxo.info Git - lorea/elgg.git/commitdiff
First version of ecml.
authorbrettp <brettp@36083f99-b078-4883-b0ff-0f9b5a30f544>
Tue, 13 Apr 2010 19:13:36 +0000 (19:13 +0000)
committerbrettp <brettp@36083f99-b078-4883-b0ff-0f9b5a30f544>
Tue, 13 Apr 2010 19:13:36 +0000 (19:13 +0000)
git-svn-id: http://code.elgg.org/elgg/trunk@5722 36083f99-b078-4883-b0ff-0f9b5a30f544

mod/ecml/README.txt [new file with mode: 0644]
mod/ecml/ecml_functions.php [new file with mode: 0644]
mod/ecml/graphics/ecml.png [new file with mode: 0644]
mod/ecml/languages/en.php [new file with mode: 0644]
mod/ecml/manifest.xml [new file with mode: 0644]
mod/ecml/start.php [new file with mode: 0644]
mod/ecml/views/default/ecml/help.php [new file with mode: 0644]
mod/ecml/views/default/ecml/input_ext.php [new file with mode: 0644]
mod/ecml/views/default/ecml/keywords/user_list.php [new file with mode: 0644]

diff --git a/mod/ecml/README.txt b/mod/ecml/README.txt
new file mode 100644 (file)
index 0000000..12b6a1c
--- /dev/null
@@ -0,0 +1,113 @@
+ECML - Elgg Custom Markup Language
+
+CONTENTS:
+       1.  Overview
+       2.  Using ECML Keywords
+               2.1  Built-in keywords
+               2.2  Entities
+               2.3  Views
+       3.  Custom ECML Keywords
+       4.  Hints and Quirks
+
+
+1.  OVERVIEW
+
+       ECML adds support for an extensible keyword     system that allows users
+       to quickly add elements and embed media in their content.  The ECML
+       control panel can be used to granualarly allow ECML keywords in certain
+       contexts and views.
+
+
+2.  USING ECML KEYWORDS
+
+       All ECML keywords are surrounded by two square brackets: [[ and ]].
+       Some keywords, like views and entity lists, take optional parameters.
+
+       Example:
+               [[user_list]] -- Display up to 10 newest users.
+
+               [[user_list: list_type=online]] -- Display up to 10 active users.
+
+               [[user_list: list_type=online, only_with_avatars=true]] -- Display
+                       up to 10 active users who have uploaded avatars.
+
+
+2.1  BUILT-IN KEYWORDS
+
+       ECML includes a few built-in keywords to get you started:
+               [[entity]] - Displays a list of users.
+
+               [[view]] - Shows the total members in your site, the currently
+                                               active members, and other fun stuff.
+
+
+2.2  Entities
+
+       You can generate a list of entities by using the [[entity]] keyword.  This
+       keyword takes similar arguments to the elgg_get_entities() function.  See
+       documentation in that function for a complete list.
+
+       Additional / changed parameters supported by keywords:
+       * owner: The username owner. (You can still use owner_guid)
+
+       Example: To generate a list of all blog posts by the user named 'admin':
+               [[entities: type=object, subtype=blog, owner=admin]]
+
+       Example: To show newest group created:
+               [[entities: type=object, subtype=group, limit=1]]
+
+
+2.1 Views
+
+       Keywords support outputting arbitrary views with the [[view]] keyword and
+       supports passing arguments as name=value pairs.
+
+       Example: Output a text input field with a default value:
+               [[view: input/text, value=This is a test!]]
+
+       NB: Do NOT quote the name or values when passing them.  Also, as of 1.8
+       using commas or = in the name or value is unsupported.
+
+
+3.0  CUSTOM FRONT PAGE KEYWORDS
+
+       Plugins can add their own keywords by replying to the 'get_keywords' hook
+       of type 'sitepages.'  Each keyword must be bound to a valid view.  Almost
+       all functionality in custom keywords could be implemented using the 'view'
+       keyword, but custom keywords provide a simple way for non-techy users to
+       include ready-made views without the fuss of knowing what they're doing.
+
+       Custom keywords support arguments in the same format as views and entities.
+       These arguments are passed to the custom view via the $vars array.  It is
+       the responsibility of the custom view to parse these arguments.
+
+       The below example creates the 'my_plugin_keyword' keyword that displays the
+       view at 'my_plugin/keyword_view.'  This is exactly the same as saying
+       [[view: my_plugin/keyword_view]] but much simpler for the user.
+
+       Example:
+               register_plugin_hook('get_keywords', 'sitepages', 'my_plugin_keywords');
+
+               function my_plugin_keywords($hook, $type, $value, $params) {
+                       $value['my_plugin_keyword'] = array(
+                               'view' => 'my_plugin/keyword_view',
+                               'description' => 'Provides the awesome My Plugin keyword'
+                       );
+
+                       return $value;
+               }
+
+
+4.  HINTS AND QUIRKS
+
+       * A custom keyword is slightly more complicated to implement, but is
+       much simpler for the end user to use.
+
+       * Custom keywords can contain only alphanumeric and the underscore
+       character.
+
+       * All keywords have limited support for passing arguments but the arguments
+       cannot contain '=' or ','.  If you need complicated arguments for a custom
+       keyword, it's better to split the functionality into multiple keywords and
+       views instead of requiring complicated arguments.
+
diff --git a/mod/ecml/ecml_functions.php b/mod/ecml/ecml_functions.php
new file mode 100644 (file)
index 0000000..dca4e03
--- /dev/null
@@ -0,0 +1,168 @@
+<?php
+/**
+ * Helper functions for ECML.
+ *
+ * @package ECML
+ * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html GNU Public License version 2
+ * @author Curverider Ltd
+ * @copyright Curverider Ltd 2008-2010
+ * @link http://elgg.org/
+ */
+
+
+/**
+ * Parse ECML keywords
+ *
+ * @param array $matches
+ * @return string html
+ */
+function ecml_parse_view_match($matches) {
+       global $CONFIG;
+
+       $view = $CONFIG->ecml_current_view;
+
+       $keyword = trim($matches[1]);
+       $params_string = trim($matches[2]);
+
+       // reject keyword if blacklisted for view or invalid
+       if (!ecml_is_valid_keyword($keyword, $view)) {
+               return $matches[0];
+       }
+
+       switch ($keyword) {
+               case 'entity':
+                       $options = ecml_keywords_parse_entity_params($params_string);
+                       // must use this lower-level function because I missed refactoring
+                       // the list entity functions for relationships.
+                       // (which, since you're here, is the only function that runs through all
+                       // possible options for elgg_get_entities*() functions...)
+                       $entities = elgg_get_entities_from_relationship($options);
+                       $content = elgg_view_entity_list($entities, count($entities), $options['offset'],
+                               $options['limit'], $options['full_view'], $options['view_type_toggle'], $options['pagination']);
+                       break;
+
+               case 'view':
+                       // parses this into an acceptable array for $vars.
+                       $info = ecml_keywords_parse_view_params($params_string);
+                       $content = elgg_view($info['view'], $info['vars']);
+
+                       break;
+
+               default:
+                       // match against custom keywords with optional args
+                       $keyword_info = $CONFIG->ecml_keywords[$keyword];
+                       $vars = ecml_keywords_tokenize_params($params_string);
+                       $content = elgg_view($keyword_info['view'], $vars);
+                       break;
+       }
+
+       // if nothing matched return the original string.
+       if (!$content) {
+               $content = $matches[0];
+       }
+
+       return $content;
+}
+
+/**
+ * Creates an array from a "name=value, name2=value2" string.
+ *
+ * @param $string
+ * @return array
+ */
+function ecml_keywords_tokenize_params($string) {
+       $pairs = array_map('trim', explode(',', $string));
+       $params = array();
+
+       foreach ($pairs as $pair) {
+               list($name, $value) = explode('=', $pair);
+
+               $name = trim($name);
+               $value = trim($value);
+
+               // normalize BOOL values
+               if ($value === 'true') {
+                       $value = TRUE;
+               } elseif ($value === 'false') {
+                       $value = FALSE;
+               }
+
+               // don't check against value since a falsy/empty value is valid.
+               if ($name) {
+                       $params[$name] = $value;
+               }
+       }
+
+       return $params;
+}
+
+/**
+ * Extract the view and vars for view: keyword
+ *
+ * @param $string
+ * @return array views, vars
+ */
+function ecml_keywords_parse_view_params($string) {
+       $vars = ecml_keywords_tokenize_params($string);
+
+       // the first element key is the view
+       $var_keys = array_keys($vars);
+       $view = $var_keys[0];
+
+       $info = array(
+               'view' => $view,
+               'vars' => $vars
+       );
+
+       return $info;
+
+}
+
+/**
+ * Returns an options array suitable for using in elgg_get_entities()
+ *
+ * @param string $string "name=value, name2=value2"
+ * @return array
+ */
+function ecml_keywords_parse_entity_params($string) {
+       $params = ecml_keywords_tokenize_params($string);
+
+       // handle some special cases
+       if (isset($params['owner'])) {
+               if ($user = get_user_by_username($params['owner'])) {
+                       $params['owner_guid'] = $user->getGUID();
+               }
+       }
+
+       // @todo probably need to add more for
+       // group -> container_guid, etc
+       return $params;
+}
+
+/**
+ * Checks granular permissions if keyword is valid for view
+ *
+ * @param unknown_type $keyword
+ * @param unknown_type $view
+ * @return bool
+ */
+function ecml_is_valid_keyword($keyword, $view = NULL) {
+       global $CONFIG;
+
+       // this isn't even a real keyword.
+       if (!isset($CONFIG->ecml_keywords[$keyword])) {
+               return FALSE;
+       }
+
+       $views = $CONFIG->ecml_permissions['views'];
+       $contexts = $CONFIG->ecml_permissions['contexts'];
+
+       // this is a blacklist, so return TRUE by default.
+       $r = TRUE;
+
+       if (isset($views[$view]) && in_array($keyword, $views[$view])) {
+               $r = FALSE;
+       }
+
+       return $r;
+}
\ No newline at end of file
diff --git a/mod/ecml/graphics/ecml.png b/mod/ecml/graphics/ecml.png
new file mode 100644 (file)
index 0000000..81de57b
Binary files /dev/null and b/mod/ecml/graphics/ecml.png differ
diff --git a/mod/ecml/languages/en.php b/mod/ecml/languages/en.php
new file mode 100644 (file)
index 0000000..d93c723
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Language definitions for ECML
+ *
+ * @package ecml
+ * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html GNU Public License version 2
+ * @author Curverider Ltd
+ * @copyright Curverider Ltd 2008-2010
+ * @link http://elgg.org/
+ */
+
+$english = array(
+       'ecml' => 'ECML',
+       'ecml:help' => 'ECML Help',
+
+       /**
+        * Key words
+        */
+       'ecml:keywords_title' => 'Keywords',
+       'ecml:keywords_instructions' =>
+               'Keywords are replaced with content when viewed.  They must be surrounded by
+               two square brackets ([[ and ]]).  You can build your own or use the ones listed below.
+               Hover over a keyword to read its description.',
+
+       'ecml:keywords_instructions_more' =>
+               '
+               <p>You can build your own keywords for views and entities.</p>
+
+               <p>[[entity: type=type, subtype=subtype, owner=username, limit=number]]<br />
+
+               EX: To show 5 blog posts by admin:<br />
+               [[entity: type=object, subtype=blog, owner=admin, limit=5]]</p>
+
+               <p>You can also specify a valid Elgg view:<br />
+               [[view: elgg_view, name=value]]</p>
+
+               <p>Ex: To show a text input with a default value:<br />
+               [[view: input/text, value=This is a default value]]</p>',
+);
+
+add_translation('en', $english);
\ No newline at end of file
diff --git a/mod/ecml/manifest.xml b/mod/ecml/manifest.xml
new file mode 100644 (file)
index 0000000..266dbcc
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<plugin_manifest>
+       <field key="name" value="ECML V2" />
+       <field key="author" value="Curverider" />
+       <field key="version" value="1.0" />
+       <field key="description" value="Elgg Custom Markup Language v2 adds support for easy customization of pages and embedding media." />
+       <field key="category" value="service" />
+       <field key="category" value="official" />
+       <field key="provides" value="ecml2" />
+       <field key="website" value="http://www.elgg.org/" />
+       <field key="copyright" value="(C) Curverider 2008-2010" />
+       <field key="licence" value="GNU Public License version 2" />
+       <field key="elgg_version" value="2009030702" />
+</plugin_manifest>
diff --git a/mod/ecml/start.php b/mod/ecml/start.php
new file mode 100644 (file)
index 0000000..0e89b4b
--- /dev/null
@@ -0,0 +1,149 @@
+<?php
+/**
+ * Provides the ECML service to plugins.
+ *
+ * @package ECML
+ * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html GNU Public License version 2
+ * @author Curverider Ltd
+ * @copyright Curverider Ltd 2008-2010
+ * @link http://elgg.org/
+ *
+ * @todo
+ *     Granular access to keywords based upon view.
+ *     Updated docs
+ *     Check for SQL injection problems.
+ *     Check entity keyword views against fullview.  Force to FALSE?
+ *
+ */
+
+/**
+ * Init ECML
+ */
+function ecml_init() {
+       require_once(dirname(__FILE__) . '/ecml_functions.php');
+       global $CONFIG;
+
+       // help page
+       register_page_handler('ecml', 'ecml_help_page_handler');
+
+       // admin access page
+       register_page_handler('ecml_admin', 'ecml_admin_page_handler');
+       register_elgg_event_handler('pagesetup', 'system', 'ecml_pagesetup');
+
+       // parse views for keywords
+       register_plugin_hook('display', 'view', 'ecml_parse_view');
+
+       // show ECML-enabled icon on free-text input areas
+       elgg_extend_view('input/longtext',  'ecml/input_ext');
+       elgg_extend_view('input/plaintext', 'ecml/input_ext');
+       //elgg_extend_view('input/text', 'ecml/input_ext');
+
+       // get register the views we want to parse for ecml
+       // @todo will need to do profiling to see if it would be faster
+       // to foreach through this list and register to specific views or
+       // do the check in a single plugin hook.
+       // Wants array('view_name' => 'Short Description')
+       $CONFIG->ecml_parse_views = trigger_plugin_hook('get_views', 'ecml', NULL, array());
+
+       // provide a few built-in ecml keywords.
+       // @todo could pull this out into an array here to save an API call.
+       register_plugin_hook('get_keywords', 'ecml', 'ecml_keyword_hook');
+
+       // grab the list of keywords and their views from plugins
+       $CONFIG->ecml_keywords = trigger_plugin_hook('get_keywords', 'ecml', NULL, array());
+
+       // grab permissions for specific views/contexts
+       // this is a black list.
+       // it's more efficient to use this as a blacklist
+       // but probably makes more sense from a UI perspective as a whitelist.
+       // uses [views][view_name] = array(keywords, not, allowed)
+       $CONFIG->ecml_permissions = array(
+               'views' => array()
+       );
+}
+
+/**
+ * Page setup. Adds admin controls to the admin panel for granular permission
+ */
+function ecml_pagesetup(){
+       if (get_context() == 'admin' && isadminloggedin()) {
+               global $CONFIG;
+               add_submenu_item(elgg_echo('ecml'), $CONFIG->wwwroot . 'pg/ecml_admin');
+       }
+}
+
+/**
+ * Display a help page for valid ECML keywords on this page.
+ *
+ * @param array $page
+ */
+function ecml_help_page_handler($page) {
+
+       $content = elgg_view('ecml/help');
+       echo page_draw(elgg_echo('ecml:help'), $content);
+}
+
+/**
+ * Display a help page for valid ECML keywords on this page.
+ *
+ * @param array $page
+ */
+function ecml_admin_page_handler($page) {
+       $content = elgg_view('ecml/admin');
+       echo page_draw(elgg_echo('ecml:admin'), $content);
+}
+
+/**
+ * Parses a registered view / context for supported keywords.
+ *
+ * @param unknown_type $hook
+ * @param unknown_type $entity_type
+ * @param unknown_type $return_value
+ * @param unknown_type $params
+ * @return string
+ */
+function ecml_parse_view($hook, $entity_type, $return_value, $params) {
+       global $CONFIG;
+
+       // give me everything that is not a ], possibly followed by a :, and surrounded by [[ ]]s
+       $keyword_regex = '/\[\[([a-z0-9_]+):?([^\]]+)?\]\]/';
+
+       if (array_key_exists($params['view'], $CONFIG->ecml_parse_views)) {
+               $CONFIG->ecml_current_view = $params['view'];
+
+               $return_value = preg_replace_callback($keyword_regex, 'ecml_parse_view_match', $return_value);
+       }
+
+       return $return_value;
+}
+
+
+/**
+ * Register some default keywords.
+ *
+ * @param unknown_type $hook
+ * @param unknown_type $entity_type
+ * @param unknown_type $return_value
+ * @param unknown_type $params
+ * @return unknown_type
+ */
+function ecml_keyword_hook($hook, $entity_type, $return_value, $params) {
+       $return_value['login_box'] = array(
+               'view' => 'account/forms/login',
+               'description' => elgg_echo('ecml:keywords:login_box')
+       );
+
+       $return_value['user_list'] = array(
+               'view' => 'ecml/keywords/user_list',
+               'description' => elgg_echo('ecml:keywords:user_list')
+       );
+
+       $return_value['site_stats'] = array(
+               'view' => 'ecml/keywords/site_stats',
+               'description' => elgg_echo('ecml:keywords:site_stats')
+       );
+
+       return $return_value;
+}
+
+register_elgg_event_handler('init', 'system', 'ecml_init');
\ No newline at end of file
diff --git a/mod/ecml/views/default/ecml/help.php b/mod/ecml/views/default/ecml/help.php
new file mode 100644 (file)
index 0000000..aed6bd2
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+/**
+ * Lists available keywords
+ *
+ * @package ECML
+ * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html GNU Public License version 2
+ * @author Curverider Ltd
+ * @copyright Curverider Ltd 2008-2010
+ * @link http://elgg.org/
+ */
+
+$keywords = $vars['config']->sitepages_keywords;
+$title = elgg_echo('ecml:keywords_title');
+$instructions = elgg_echo('ecml:keywords_instructions');
+$more_info = elgg_echo('ecml:keywords_instructions_more');
+
+
+
+$keywords_html = '';
+foreach ($keywords as $keyword => $info) {
+       $desc = htmlentities($info['description']);
+       $keywords_html .= "<li><acronym title=\"$desc\">[[$keyword]]</acronym></li>";
+}
+
+echo "
+<h3>$title</h3>
+<p>$instructions</p>
+$more_info
+<ul>
+       $keywords_html
+</ul>
+";
\ No newline at end of file
diff --git a/mod/ecml/views/default/ecml/input_ext.php b/mod/ecml/views/default/ecml/input_ext.php
new file mode 100644 (file)
index 0000000..03941fb
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+/**
+ * Displays an ECML icon on ECML-enabled forms
+ *
+ * @package ECML
+ * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html GNU Public License version 2
+ * @author Curverider Ltd
+ * @copyright Curverider Ltd 2008-2010
+ * @link http://elgg.org/
+ */
+
+$docs_href = "{$vars['url']}pg/ecml";
+?>
+<a href="<?php echo $docs_href; ?>" target="_new"><img src="<?php echo $vars['url']; ?>mod/ecml/graphics/ecml.png" width="50" height="15" alt="ECML" /></a>
\ No newline at end of file
diff --git a/mod/ecml/views/default/ecml/keywords/user_list.php b/mod/ecml/views/default/ecml/keywords/user_list.php
new file mode 100644 (file)
index 0000000..2f2f09c
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Lists users
+ *
+ * @package SitePages
+ * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html GNU Public License version 2
+ * @author Curverider Ltd
+ * @copyright Curverider Ltd 2008-2010
+ * @link http://elgg.org/
+ */
+
+$only_with_avatars = (isset($vars['only_with_avatars'])) ? $vars['only_with_avatars'] : TRUE;
+$list_type = (isset($vars['list_type'])) ? $vars['list_type'] : 'newest';
+$limit = (isset($vars['limit'])) ? $vars['limit'] : 10;
+
+$options = array(
+       'type' => 'user',
+       'limit' => $limit
+);
+
+if ($only_with_avatars == TRUE) {
+       $options['metadata_name_value_pairs'] = array('name' => 'icontime', 'operand' => '!=', 'value' => 0);
+}
+
+switch ($list_type) {
+       case 'newest':
+               $options['order_by'] = 'e.time_created DESC';
+               break;
+
+       case 'online':
+               // show people with a last action of < 10 minutes.
+               $last_action = time() - 10 * 60;
+               $options['joins'] = array("JOIN {$vars['config']->dbprefix}users_entity ue on ue.guid = e.guid");
+               $options['wheres'] = array("ue.last_action > $last_action");
+               break;
+
+       case 'random':
+               $options['order_by'] = 'RAND()';
+               break;
+
+       default:
+               break;
+}
+
+$users = elgg_get_entities_from_metadata($options);
+
+echo elgg_view_entity_list($users, count($users), 0, $limit, FALSE, FALSE, FALSE);
\ No newline at end of file