<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[BuddyPress][7097] trunk: Introduces automatic download of BuddyPress translations from translate.wordpress.org.</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://buddypress.trac.wordpress.org/changeset/7097">7097</a></dd>
<dt>Author</dt> <dd>djpaul</dd>
<dt>Date</dt> <dd>2013-05-22 23:24:01 +0000 (Wed, 22 May 2013)</dd>
</dl>
<h3>Log Message</h3>
<pre>Introduces automatic download of BuddyPress translations from translate.wordpress.org. Fixes <a href="http://buddypress.trac.wordpress.org/ticket/4857">#4857</a>
BuddyPress now uses cron to periodically check if the version of the current locale's translation on translate.wordpress.org is more recent than the installed version.
If an update is available, the "Updates" badge's count in the WordPress admin menu is incremented, and a new button on the Updates screen allows a site admin to update the translation to that latest version.
This improves user experience for everyone who wants to use BuddyPress in their native language.
Thanks to markoheijnen for help making changes to GlotPress to enable this functionality.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkbpcorebpcoreadminphp">trunk/bp-core/bp-core-admin.php</a></li>
<li><a href="#trunkbpcorebpcorefunctionsphp">trunk/bp-core/bp-core-functions.php</a></li>
<li><a href="#trunkbpcorebpcoreoptionsphp">trunk/bp-core/bp-core-options.php</a></li>
<li><a href="#trunkbploaderphp">trunk/bp-loader.php</a></li>
</ul>
<h3>Added Paths</h3>
<ul>
<li><a href="#trunkbpcoreadminbpcoretranslationsphp">trunk/bp-core/admin/bp-core-translations.php</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkbpcoreadminbpcoretranslationsphp"></a>
<div class="addfile"><h4>Added: trunk/bp-core/admin/bp-core-translations.php (0 => 7097)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/bp-core/admin/bp-core-translations.php (rev 0)
+++ trunk/bp-core/admin/bp-core-translations.php 2013-05-22 23:24:01 UTC (rev 7097)
</span><span class="lines">@@ -0,0 +1,274 @@
</span><ins>+<?php
+/**
+ * Handles automatic download of translations
+ *
+ * @package BuddyPress
+ * @subpackage CoreAdministration
+ * @since BuddyPress (1.8)
+ */
+
+// Exit if accessed directly
+if ( ! defined( 'ABSPATH' ) ) exit;
+
+/**
+ * Creates an instance of the BP_Translate class that downloads translations for BuddyPress automatically
+ *
+ * @since BuddyPress (1.8)
+ */
+function bp_automatic_translations() {
+ if ( ! current_user_can( 'update_plugins' ) )
+ return;
+
+ if ( ! is_admin() || ( is_multisite() && ! is_network_admin() ) )
+ return;
+
+ // Store a reference to the BP_Translate object in the BP global for other plugins
+ buddypress()->translate = BP_Translate::get_instance();
+}
+add_action( 'bp_admin_init', 'bp_automatic_translations' );
+
+/**
+ * If we're in the WordPress dashboard, and a pending translation is available, bump the update count.
+ *
+ * This has to be hooked before admin_init due to wp_get_update_data() being invoked in wp-admin/menu.php before the admin_init action is called.
+ *
+ * @since BuddyPress (1.8)
+ */
+function bp_admin_maybe_bump_update_count() {
+ if ( ! current_user_can( 'update_plugins' ) )
+ return;
+
+ if ( ! is_admin() || ( is_multisite() && ! is_network_admin() ) )
+ return;
+
+ add_filter( 'wp_get_update_data', array( 'BP_Translate', 'maybe_bump_update_count' ) );
+}
+add_action( 'bp_init', 'bp_admin_maybe_bump_update_count' );
+
+/**
+ * Fetch translations from http://translate.wordpress.org/ and display an update prompt on the admin dashboard.
+ *
+ * @since BuddyPress (1.8)
+ */
+class BP_Translate {
+
+ /**
+ * Singleton instance of the BP_Translate class
+ *
+ * @since BuddyPress (1.8)
+ * @var BP_Translate
+ */
+ private static $instance;
+
+ /**
+ * Return the singleton instance of the BP_Translate class
+ *
+ * @return BP_Translate
+ * @since BuddyPress (1.8)
+ */
+ static public function get_instance() {
+ if ( ! self::$instance )
+ self::$instance = new BP_Translate;
+
+ return self::$instance;
+ }
+
+ /**
+ * Constructor
+ *
+ * @since BuddyPress (1.8)
+ */
+ public function __construct() {
+ $this->register_actions();
+ $this->register_cron(); // Intentionally after actions
+ }
+
+ /**
+ * Hook into actions necessary to automate the translation process and customise wp-admin
+ *
+ * @since BuddyPress (1.8)
+ */
+ protected function register_actions() {
+ add_action( 'core_upgrade_preamble', array( __CLASS__, 'updates_screen' ) );
+ add_action( 'update-core-custom_do-update-buddypress-translation', array( __CLASS__, 'updates_screen_iframe' ) );
+ add_action( 'update-custom_update-buddypress-translation', array( __CLASS__, 'update_translation' ) );
+ }
+
+ /**
+ * Register cron task to check for language updates
+ *
+ * @since BuddyPress (1.8)
+ */
+ protected function register_cron() {
+ if ( ! wp_next_scheduled( 'bp_translate_update_check' ) )
+ wp_schedule_event( time(), 'daily', 'bp_translate_update_check' );
+ }
+
+ /**
+ * Get the current locale
+ *
+ * @return string
+ * @since BuddyPress (1.8)
+ */
+ static public function get_locale() {
+ return apply_filters( 'buddypress_locale', get_locale() );
+ }
+
+ /**
+ * Get the GlotPress locale code for the current locale
+ *
+ * @return string|bool Returns bool if an error occured, otherwise the GlotPress locale as a string
+ * @since BuddyPress (1.8)
+ */
+ static public function get_glotpress_locale() {
+ static $glotpress_locale;
+ if ( ! empty( $glotpress_locale ) )
+ return $glotpress_locale;
+
+ // Get the list of available translations from translate.wordpress.org
+ $translations = wp_remote_get( sprintf( 'https://translate.wordpress.org/api/projects/buddypress/%1$s', buddypress()->glotpress_version ) );
+ if ( is_wp_error( $translations ) || wp_remote_retrieve_response_code( $translations ) !== 200 )
+ return false;
+
+ $translations = json_decode( wp_remote_retrieve_body( $translations ) );
+ if ( is_null( $translations ) )
+ return false;
+
+ // Does the requested $locale have an available translation?
+ $translations = array_shift( wp_list_filter( $translations->translation_sets, array( 'wp_locale' => BP_Translate::get_locale() ) ) );
+ if ( empty( $translations ) )
+ return false;
+
+ $glotpress_locale = $translations->locale;
+ return $glotpress_locale;
+ }
+
+ /**
+ * If in the WordPress dashboard, maybe bump the "available updates" count if there's a pending translation.
+ *
+ * @param array $data Counts and UI strings for available updates
+ * @return array
+ * @since BuddyPress (1.8)
+ */
+ static public function maybe_bump_update_count( $data ) {
+ if ( current_user_can( 'update_plugins' ) && bp_is_translation_update_pending() )
+ $data['counts']['total']++;
+
+ return $data;
+ }
+
+ /**
+ * If we have a pending translation, display a message on the wp-admin/update-core.php screen.
+ *
+ * @since BuddyPress (1.8)
+ */
+ static public function updates_screen() {
+
+ if ( BP_Translate::get_locale() === 'en_US' || ! bp_is_translation_update_pending() )
+ return;
+ ?>
+ <h3><?php _e( 'BuddyPress Translation', 'buddypress' ); ?></h3>
+ <p><?php _e( 'An updated version of the current BuddyPress translation is available. Click “Update Translation”.', 'buddypress' ); ?></p>
+
+ <form method="post" action="<?php echo esc_url( 'update-core.php?action=do-update-buddypress-translation' ); ?>" name="update-buddypress-translation" class="upgrade">
+ <?php wp_nonce_field( 'update-buddypress-translation' ); ?>
+
+ <p><input class="button" type="submit" value="<?php esc_attr_e( 'Update Translation', 'buddypress' ); ?>" name="upgrade" /></p>
+ </form>
+ <?php
+ }
+
+ /**
+ * We're going to update the BuddyPress translation; output an iframe in which the magic will happen.
+ *
+ * This copies the implementation for the Plugin and Theme updates wherein the work is done in a separate
+ * request that is iframed in. This allows WordPress to recover from any errors during the process.
+ *
+ * @since BuddyPress (1.8)
+ */
+ static public function updates_screen_iframe() {
+
+ if ( ! current_user_can( 'update_plugins' ) )
+ wp_die( __( 'You do not have sufficient permissions to update this site.', 'buddypress' ) );
+
+ check_admin_referer( 'update-buddypress-translation' );
+
+ // If no pending translation updates, redirect away.
+ if ( ! bp_is_translation_update_pending() ) {
+ wp_redirect( admin_url('update-core.php') );
+ exit;
+ }
+
+ require_once( ABSPATH . 'wp-admin/admin-header.php' );
+ $title = __( 'Update BuddyPress Translation', 'buddypress' );
+ $url = wp_nonce_url( 'update.php?action=update-buddypress-translation', 'update-buddypress-translation' );
+
+ echo '<div class="wrap">';
+ screen_icon( 'plugins' );
+ echo '<h2>' . esc_html( $title ) . '</h2>';
+ echo '<iframe src=' . esc_url( $url ) . ' style="width: 100%; height: 100%; min-height: 750px;" frameborder="0"></iframe>';
+ echo '</div>';
+
+ include( ABSPATH . 'wp-admin/admin-footer.php' );
+ }
+
+ /**
+ * Download the latest version of the current locale's translation from translate.wordpress.org
+ *
+ * @since BuddyPress (1.8)
+ */
+ static public function update_translation() {
+
+ // @todo Not sure if this does anything
+ if ( ! defined( 'IFRAME_REQUEST' ) )
+ define( 'IFRAME_REQUEST', true );
+
+ if ( ! current_user_can( 'update_plugins' ) || BP_Translate::get_locale() === 'en_US' )
+ wp_die( __( 'You do not have sufficient permissions to update this site.', 'buddypress' ) );
+
+ check_admin_referer( 'update-buddypress-translation' );
+ iframe_header();
+
+ echo '<p>' . __( 'The update process is starting. This process may take a while on some hosts, so please be patient.', 'buddypress' ) . '</p>';
+ echo '<h4>' . __( 'Updating BuddyPress Translation', 'buddypress' ) . '</h4>';
+
+ // Download the .mo to a local temporary file
+ $url = 'https://translate.wordpress.org/projects/buddypress/%1$s/%2$s/default/export-translations?format=mo';
+ $tmp = download_url( sprintf( $url, buddypress()->glotpress_version, BP_Translate::get_glotpress_locale() ) );
+
+ if ( is_wp_error( $tmp ) ) {
+ $css_class = 'error';
+ $message = __( 'Error: failure updating translation.', 'buddypress' );
+ $message .= '</p><p><strong>' . $tmp->get_error_message() . '</strong>';
+
+ } else {
+ $css_class = 'updated';
+ $message = __( 'Translation updated succesfully!', 'buddypress' );
+ $upload_dir = wp_upload_dir();
+ $new_file = sprintf( '%s/buddypress/buddypress-%s.mo', $upload_dir['basedir'], BP_Translate::get_locale() );
+
+ // Check the target folder exists
+ @mkdir( $upload_dir['basedir'] . '/buddypress' );
+
+ // Move the file into place
+ @copy( $tmp, $new_file );
+ @unlink( $tmp );
+
+ // Store the current timestamp for future checks, for the IF-MODIFIED-SINCE header
+ bp_update_option( '_bp_translation_version', time() );
+
+ // Clear the pending translation flag
+ bp_delete_option( '_bp_translation_pending' );
+ }
+ ?>
+
+ <div class="<?php echo esc_attr( $css_class ); ?>">
+ <p><?php echo $message; ?></p>
+ </div>
+
+ <p><a href="<?php echo self_admin_url( 'update-core.php' ); ?>" target="_parent"><?php _e( 'Return to WordPress Updates', 'buddypress' ); ?></a></p>
+
+ <?php
+ iframe_footer();
+ }
+}
</ins></span></pre></div>
<a id="trunkbpcorebpcoreadminphp"></a>
<div class="modfile"><h4>Modified: trunk/bp-core/bp-core-admin.php (7096 => 7097)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/bp-core/bp-core-admin.php 2013-05-22 05:35:15 UTC (rev 7096)
+++ trunk/bp-core/bp-core-admin.php 2013-05-22 23:24:01 UTC (rev 7097)
</span><span class="lines">@@ -93,11 +93,12 @@
</span><span class="cx"> * @access private
</span><span class="cx"> */
</span><span class="cx"> private function includes() {
</span><del>- require( $this->admin_dir . 'bp-core-actions.php' );
- require( $this->admin_dir . 'bp-core-settings.php' );
- require( $this->admin_dir . 'bp-core-functions.php' );
- require( $this->admin_dir . 'bp-core-components.php' );
- require( $this->admin_dir . 'bp-core-slugs.php' );
</del><ins>+ require( $this->admin_dir . 'bp-core-actions.php' );
+ require( $this->admin_dir . 'bp-core-settings.php' );
+ require( $this->admin_dir . 'bp-core-functions.php' );
+ require( $this->admin_dir . 'bp-core-components.php' );
+ require( $this->admin_dir . 'bp-core-slugs.php' );
+ require( $this->admin_dir . 'bp-core-translations.php' );
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> /**
</span></span></pre></div>
<a id="trunkbpcorebpcorefunctionsphp"></a>
<div class="modfile"><h4>Modified: trunk/bp-core/bp-core-functions.php (7096 => 7097)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/bp-core/bp-core-functions.php 2013-05-22 05:35:15 UTC (rev 7096)
+++ trunk/bp-core/bp-core-functions.php 2013-05-22 23:24:01 UTC (rev 7097)
</span><span class="lines">@@ -1267,19 +1267,20 @@
</span><span class="cx"> * @package BuddyPress Core
</span><span class="cx"> */
</span><span class="cx"> function bp_core_load_buddypress_textdomain() {
</span><del>- $locale = apply_filters( 'buddypress_locale', get_locale() );
- $mofile = sprintf( 'buddypress-%s.mo', $locale );
- $mofile_global = WP_LANG_DIR . '/' . $mofile;
- $mofile_local = BP_PLUGIN_DIR . 'bp-languages/' . $mofile;
</del><ins>+ $locale = apply_filters( 'buddypress_locale', get_locale() );
+ $mofile = sprintf( 'buddypress-%s.mo', $locale );
+ $uploads_dir = wp_upload_dir();
</ins><span class="cx">
</span><del>- if ( file_exists( $mofile_global ) )
- return load_textdomain( 'buddypress', $mofile_global );
- elseif ( file_exists( $mofile_local ) )
- return load_textdomain( 'buddypress', $mofile_local );
- else
- return false;
</del><ins>+ $translation_paths = array(
+ WP_LANG_DIR . "/{$mofile}",
+ BP_PLUGIN_DIR . "bp-languages/{$mofile}",
+ $uploads_dir['basedir'] . "/buddypress/{$mofile}",
+ );
+
+ foreach ( $translation_paths as $path )
+ load_textdomain( 'buddypress', $path );
</ins><span class="cx"> }
</span><del>-add_action ( 'bp_core_loaded', 'bp_core_load_buddypress_textdomain' );
</del><ins>+add_action( 'bp_core_loaded', 'bp_core_load_buddypress_textdomain' );
</ins><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> * A javascript free implementation of the search functions in BuddyPress
</span><span class="lines">@@ -1360,3 +1361,36 @@
</span><span class="cx"> <?php
</span><span class="cx"> }
</span><span class="cx"> add_action( 'wp_footer', 'bp_core_print_generation_time' );
</span><ins>+
+/**
+ * Find out if there's a newer translation available for this site on translate.wordpress.org
+ *
+ * @since BuddyPress (1.8)
+ */
+function bp_core_check_for_updated_translation() {
+ require( buddypress()->plugin_dir . 'bp-core/admin/bp-core-translations.php' );
+
+ $locale = BP_Translate::get_locale();
+ if ( 'en_US' === $locale )
+ return;
+
+ // No point checking if we know there's an updated translation
+ if ( bp_is_translation_update_pending() )
+ return;
+
+ $locale = BP_Translate::get_glotpress_locale();
+ if ( ! $locale )
+ return;
+
+ $url = 'https://translate.wordpress.org/projects/buddypress/%1$s/%2$s/default/export-translations?format=mo';
+ $args = bp_get_translation_version() ? array( 'headers' => 'If-Modified-Since: ' . gmdate( 'D, d M Y H:i:s', bp_get_translation_version() ) . ' GMT' ) : array();
+
+ // Check version of translation on translate.wordpress.org
+ $response = wp_remote_head( sprintf( $url, buddypress()->glotpress_version, $locale ), $args );
+ if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) === 304 || wp_remote_retrieve_response_code( $response ) !== 200 )
+ return;
+
+ // An updated translation is available
+ bp_update_option( '_bp_translation_pending', true );
+}
+add_action( 'bp_translate_update_check', 'bp_core_check_for_updated_translation' );
</ins></span></pre></div>
<a id="trunkbpcorebpcoreoptionsphp"></a>
<div class="modfile"><h4>Modified: trunk/bp-core/bp-core-options.php (7096 => 7097)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/bp-core/bp-core-options.php 2013-05-22 05:35:15 UTC (rev 7096)
+++ trunk/bp-core/bp-core-options.php 2013-05-22 23:24:01 UTC (rev 7097)
</span><span class="lines">@@ -64,6 +64,12 @@
</span><span class="cx"> // The ID for the current theme package.
</span><span class="cx"> '_bp_theme_package_id' => 'legacy',
</span><span class="cx">
</span><ins>+ // Timestamp of the the current translation from translate.wordpress.org
+ '_bp_translation_version' => 0,
+
+ // Is there a more recent translation available on translate.wordpress.org?
+ '_bp_translation_pending' => false,
+
</ins><span class="cx"> /** Groups ************************************************************/
</span><span class="cx">
</span><span class="cx"> // @todo Move this into the groups component
</span><span class="lines">@@ -536,3 +542,26 @@
</span><span class="cx"> function bp_get_theme_package_id( $default = 'legacy' ) {
</span><span class="cx"> return apply_filters( 'bp_get_theme_package_id', bp_get_option( '_bp_theme_package_id', $default ) );
</span><span class="cx"> }
</span><ins>+
+/**
+ * Get the timestamp of the the current translation from translate.wordpress.org
+ *
+ * @param int $default Optional; default value 0.
+ * @return int Unix timestamp
+ * @since BuddyPress (1.8)
+ */
+function bp_get_translation_version( $default = 0 ) {
+ return apply_filters( 'bp_get_translation_version', (int) bp_get_option( '_bp_translation_version', $default ) );
+}
+
+/**
+ * Does translate.wordpress.org have a more recent translation available on translate.wordpress.org?
+ *
+ * @param bool $default Optional; defaults to false.
+ * @return bool
+ * @see BP_Translate->check_for_updated_translation()
+ * @since BuddyPress (1.8)
+ */
+function bp_is_translation_update_pending( $default = false ) {
+ return apply_filters( 'bp_is_translation_update_pending', (bool) bp_get_option( '_bp_translation_pending', $default ) );
+}
</ins></span></pre></div>
<a id="trunkbploaderphp"></a>
<div class="modfile"><h4>Modified: trunk/bp-loader.php (7096 => 7097)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/bp-loader.php 2013-05-22 05:35:15 UTC (rev 7096)
+++ trunk/bp-loader.php 2013-05-22 23:24:01 UTC (rev 7097)
</span><span class="lines">@@ -280,6 +280,7 @@
</span><span class="cx">
</span><span class="cx"> $this->version = '1.8-bleeding-7013';
</span><span class="cx"> $this->db_version = 6080;
</span><ins>+ $this->glotpress_version = 'dev';
</ins><span class="cx">
</span><span class="cx"> /** Loading ***********************************************************/
</span><span class="cx">
</span></span></pre>
</div>
</div>
</body>
</html>