/** save the modified content data of this module linked to node $node_id
 *
 * this validates and saves the data that was submitted by the user.
 * If validation fails, or storing the data doesn't work, the flag $edit_again
 * is set to TRUE and the return value is FALSE.
 *
 * If the user has cancelled the operation, the flag $edit_again is set to FALSE
 * and the return value is also FALSE.
 *
 * If the modified data is stored successfully, the return value is TRUE (and
 * the value of $edit_again is a don't care).
 *
 * Here is a summary of return values.
 *
 *  - retval = TRUE ==> data saved successfully
 *  - retval = FALSE && edit_again = TRUE ==> re-edit the data, show the edit dialog again
 *  - retval = FALSE && edit_again = FALSE ==> cancelled, do nothing
 *
 * @param object &$output collects the html output (if any)
 * @param int $area_id the area in which $node_id resides
 * @param int $node_id the node to which the content is connected
 * @param array $module the module record straight from the database
 * @param bool $viewonly if TRUE, editing and hence saving is not allowed
 * @param bool &$edit_again set to TRUE if we need to edit the content again, FALSE otherwise
 * @return bool TRUE on success + output stored via $output, FALSE otherwise
 */
function crew_save(&$output, $area_id, $node_id, $module, $viewonly, &$edit_again)
{
    global $USER;
    $retval = TRUE;
    // assume success
    $module_id = intval($module['module_id']);
    $node_id = intval($node_id);
    // 1 -- bail out if cancelled or viewonly
    if (isset($_POST['button_cancel']) || $viewonly) {
        $edit_again = FALSE;
        return FALSE;
    }
    // 2 -- redo if invalid data was submitted
    $dialogdef = crew_get_dialogdef($output, $viewonly, $module_id, $area_id, $node_id, $USER->user_id);
    if (!dialog_validate($dialogdef)) {
        // there were errors, show them to the user and ask caller to do it again
        foreach ($dialogdef as $k => $item) {
            if (isset($item['errors']) && $item['errors'] > 0) {
                $output->add_message($item['error_messages']);
            }
        }
        $edit_again = TRUE;
        return FALSE;
    }
    // 3 -- actually save the new (plain) settings (always)
    $now = strftime('%Y-%m-%d %T');
    $table = 'workshops';
    $fields = array('header' => $dialogdef['header']['value'], 'introduction' => $dialogdef['introduction']['value'], 'visibility' => $dialogdef['visibility']['value'], 'mtime' => $now, 'muser_id' => $USER->user_id);
    $where = array('node_id' => intval($node_id));
    if (db_update($table, $fields, $where) === FALSE) {
        logger(sprintf('%s(): error saving config value: %s', __FUNCTION__, db_errormessage()));
        $edit_again = TRUE;
        $retval = FALSE;
    }
    // 4 -- save or delete the changed ACLs and maybe add new ones too
    $table = 'acls_modules_nodes';
    $where = array('acl_id' => 0, 'node_id' => $node_id, 'module_id' => $module_id);
    $fields = $where;
    foreach ($dialogdef as $k => $item) {
        if (!isset($item['acl_id'])) {
            continue;
        }
        $acl_id = intval($item['acl_id']);
        $value = intval($item['value']);
        $old_value = intval($item['old_value']);
        $dbretval = TRUE;
        // assume success
        if ($value != 0) {
            if (is_null($item['old_value'])) {
                // need to add a new record
                $fields['permissions_modules'] = $value;
                $fields['acl_id'] = $acl_id;
                $dbretval = db_insert_into($table, $fields);
            } else {
                if ($value != $old_value) {
                    // need to update existing record
                    $where['acl_id'] = $acl_id;
                    $dbretval = db_update($table, array('permissions_modules' => $value), $where);
                }
            }
        } else {
            if (!is_null($item['old_value'])) {
                // delete existing record because the value is now 0
                $where['acl_id'] = $acl_id;
                $dbretval = db_delete($table, $where);
            }
        }
        if ($dbretval === FALSE) {
            $messages[] = __FUNCTION__ . '(): ' . db_errormessage();
            $edit_again = TRUE;
            $retval = FALSE;
        }
    }
    return $retval;
}
/** validate the data entered by the visitor
 *
 * Other than the standard validation we check for at least 1 @-sign in
 * the email address. (It is non-trivial to validate an email address,
 * it may not even be possible to do it in a single regular expression.
 * See http://stackoverflow.com/questions/201323 for more information.)
 *
 * @param object &$dialogdef defines the dialog and will hold POSTed values
 * @return bool TRUE if valid, else FALSE + messages added to dialogdef
 */
function mailpage_view_dialog_validate(&$dialogdef)
{
    $retval = TRUE;
    // assume success
    // 1 -- check the usual suspects (side-effect: fetch POSTed values in dialogdef)
    if (!dialog_validate($dialogdef)) {
        $retval = FALSE;
    }
    // 2 -- special case: there must be at least 1 @-sign in the email field
    if (strpos($dialogdef['email']['value'], '@') === FALSE) {
        // no @-sign in mail address
        $retval = FALSE;
        ++$dialogdef['email']['errors'];
        $params = array('{FIELD}' => str_replace('~', '', $dialogdef['email']['label']));
        $dialogdef['email']['error_messages'][] = t('validate_invalid', '', $params);
    }
    return $retval;
}
/** save the modified content data of this module linked to node $node_id
 *
 * this validates and saves the data that was submitted by the user.
 * If validation fails, or storing the data doesn't work, the flag $edit_again
 * is set to TRUE and the return value is FALSE.
 *
 * If the user has cancelled the operation, the flag $edit_again is set to FALSE
 * and the return value is also FALSE.
 *
 * If the modified data is stored successfully, the return value is TRUE (and
 * the value of $edit_again is a don't care).
 *
 * Here is a summary of return values.
 *
 *  - retval = TRUE ==> data saved successfully
 *  - retval = FALSE && edit_again = TRUE ==> re-edit the data, show the edit dialog again
 *  - retval = FALSE && edit_again = FALSE ==> cancelled, do nothing
 *
 * @param object &$output collects the html output (if any)
 * @param int $area_id the area in which $node_id resides
 * @param int $node_id the node to which the content is connected
 * @param array $module the module record straight from the database
 * @param bool $viewonly if TRUE, editing and hence saving is not allowed
 * @param bool &$edit_again set to TRUE if we need to edit the content again, FALSE otherwise
 * @return bool TRUE on success + output stored via $output, FALSE otherwise
 */
function snapshots_save(&$output, $area_id, $node_id, $module, $viewonly, &$edit_again)
{
    global $USER;
    // 1 -- bail out if cancelled or viewonly
    if (isset($_POST['button_cancel']) || $viewonly) {
        $edit_again = FALSE;
        return FALSE;
    }
    // 2 -- redo if invalid data was submitted
    $invalid = FALSE;
    $dialogdef = snapshots_get_dialogdef($viewonly);
    if (!dialog_validate($dialogdef)) {
        $invalid = TRUE;
    }
    if (!snapshots_check_path($dialogdef['snapshots_path'], $area_id, $node_id)) {
        $invalid = TRUE;
    }
    if (isset($dialogdef['snapshots_path']['warnings']) && $dialogdef['snapshots_path']['warnings'] > 0) {
        $output->add_message($dialogdef['snapshots_path']['warning_messages']);
    }
    // show errors to the user and redo
    if ($invalid) {
        foreach ($dialogdef as $k => $item) {
            if (isset($item['errors']) && $item['errors'] > 0) {
                $output->add_message($item['error_messages']);
            }
        }
        $edit_again = TRUE;
        return FALSE;
    }
    // 3 -- actually save the new setting
    $now = strftime('%Y-%m-%d %T');
    $table = 'snapshots';
    $fields = array('header' => $dialogdef['header']['value'], 'introduction' => $dialogdef['introduction']['value'], 'snapshots_path' => $dialogdef['snapshots_path']['value'], 'variant' => $dialogdef['variant']['value'], 'dimension' => intval($dialogdef['dimension']['value']), 'mtime' => $now, 'muser_id' => $USER->user_id);
    $where = array('node_id' => intval($node_id));
    if (db_update($table, $fields, $where) === FALSE) {
        logger(sprintf('%s(): error saving config value: %s', __FUNCTION__, db_errormessage()));
        $edit_again = TRUE;
        return FALSE;
    } else {
        return TRUE;
        // $edit_again is a don't care
    }
}
/** show the (visually almost) empty page and load or continue with the JS popup window
 *
 * this routine is responsible for showing an 'empty' page and maybe for generating
 * a JS popup window (if $first==TRUE). The 'empty' page contains only a form with
 * single textarea. However, this textarea is not displayed (display:none) so the
 * casual user sees nothing (but obviously without CSS it is a different matter).
 * This textarea is used by the CREW code to store the edited document before
 * submitting the form. Since there are no buttons of any kind, it is completely
 * up to the JS code to generate the necessary DOM elements that are required to
 * successfully save the document.
 *
 * If $first is TRUE, we have to setup the popup window. This is quite complicated
 * because generate the necessary JS-code at runtime using JS. One of the reasons
 * is that I want to set the correct translations in the popup window. There may
 * be an easier way.
 *
 * The Websocket protocol is used to talk to the Websocket server which is configured
 * for this site. This setting can be manipulated using the Module Manager. In order
 * to authenticate ourselves against the websocket server we use the following mechanism.
 * There are a few important variables used in authenticating:
 *
 *  - $origin: this is the website's hostname as seen by the user's browser
 *  - $request_uri: a string that uniquely identifies the node within the origin
 *  - $full_name: the full name of the current user (ie. $USER->full_name) 
 *  - $username: the (short) name/userid of the curent user (ie. $USER->username)
 *  - $request_date: the current time (GMT) in the format "yyyy-mm-dd hh:mm:ss".
 *
 * and also
 *
 *  - $secret_key: a secret shared with the Websocket server
 *  - $location: the URL of the Websocket server
 *
 * The authentication works as follows. The variables $origin, $request_uri, $full_name,
 * $username and $request_date are concatenated in a $message. Then the $message and
 * the $secret_key are used to calculate a hashed message authentication code (HMAC)
 * according to RFC2104 (see function {@see hmac()} in waslib.php).
 *
 * When connecting to the Websocket server the parameters $request_uri, $full_name,
 * $username and $request_date are sent, together with the HMAC. The server then
 * calculates the HMAC too and if it matches the HMAC that was sent, access is
 * granted.
 *
 * Note that the variable $origin is only used here to calculate the HMAC; it is
 * not sent to the Websocket server like the other parameters. Instead we use the
 * Origin as seen by the user's web browser. Obviously the two should match or else
 * authentication fails. This way we check the browser's idea of where the web page
 * is located. Also note that we made the current date/time part of the HMAC. That
 * is done to prevent replay-attacks (the other variables are quasi-static between
 * CREW editing sessions). It is up to the Websocket server to determine if the
 * timestamp is (still) valid or not. This depends on a certain clock synchronisation
 * between the webserver and the Websocket server.
 *
 * Also note that the shared secret never leaves the webserver, only the hashed
 * message is sent from webserver to Websocket server. However, the secret has to
 * be the same on both ends.
 *
 * @param object &$theme collects the (html) output
 * @param int $module_id identifies the crew module (need that for getting module properties)
 * @param bool $first if TRUE we generate code to generate a popup
 * @return bool TRUE on success+output generated via $theme, FALSE otherwise
 */
function crew_view_show_edit(&$theme, $module_id, $first = FALSE)
{
    global $USER, $WAS_SCRIPT_NAME, $CFG;
    // 1A -- fetch the latest version of the document (we always need that)...
    $node_id = intval($theme->node_record['node_id']);
    if (($record = crew_view_get_workshop_data($node_id)) === FALSE) {
        $theme->add_message(t('error_retrieving_workshop_data', 'm_crew'));
        return FALSE;
    }
    // 1B -- and tell the user the date/time/user of latest update in content area
    $params = array('{USERNAME}' => is_null($record['username']) ? $record['muser_id'] : $record['username'], '{FULL_NAME}' => is_null($record['full_name']) ? $record['muser_id'] : $record['full_name'], '{DATIM}' => $record['mtime']);
    $attr = array('class' => 'crew_datim');
    $theme->add_content(html_tag('p', $attr, t('last_updated_by', 'm_crew', $params)));
    // 1C -- prepare a hidden textarea with the current document text
    /* <noscript>requires javascript</noscript>
     * <div>
     *   <form>
     *     <textarea>$document</textarea>
     *   </form>
     * </div>
     */
    $theme->add_content(html_tag('noscript', '', t('crew_requires_js_and_ws', 'm_crew')));
    $attr = array('id' => 'crew_start_edit', 'style' => 'display: none;');
    $theme->add_content(html_tag('div', $attr));
    $href = was_node_url($theme->node_record);
    $attr = array('id' => 'frmEdit');
    $theme->add_content(html_form($href, 'post', $attr));
    $attr = array('id' => 'txtText', 'rows' => 10, 'cols' => 80, 'name' => 'text');
    $theme->add_content(html_tag('textarea', $attr, htmlspecialchars($record['document'])));
    $theme->add_content(html_form_close());
    $theme->add_content(html_tag_close('div'));
    // At this point we're done IF this was a repeat call.
    // If it was the first call we need to do some more, like popping up the edit window
    if (!$first) {
        return TRUE;
    }
    // Still here, so this is the first time
    // 2 -- prepare all information for popup
    // 2A -- which skin?
    $dialogdef = crew_view_dialogdef();
    if (!dialog_validate($dialogdef)) {
        // somehow an error; default to first skin
        $value = '0';
    } else {
        $value = $dialogdef['skin']['value'];
    }
    $skin = $dialogdef['skin']['options'][$value]['css'];
    // 2B -- which location,origin,secret (from module_properties)
    $table = 'modules_properties';
    $fields = array('name', 'value');
    $where = array('module_id' => $module_id);
    $order = array('sort_order');
    $keyfield = 'name';
    if (($properties = db_select_all_records($table, $fields, $where, $order, $keyfield)) === FALSE) {
        logger(sprintf('%s(): module properties error: %s', __FUNCTION__, db_errormessage()));
        $theme->add_message(t('error_retrieving_workshop_data', 'm_crew'));
        return FALSE;
    }
    $org = $properties['origin']['value'];
    $loc = $properties['location']['value'];
    $secret = $properties['secret']['value'];
    // 2C -- prepare variables for and perform hmac calculation
    $workshop = trim($record['header']);
    if (empty($workshop)) {
        $workshop = trim($node_record['link_text']);
    }
    $uri = sprintf('%s/%d/%s', $WAS_SCRIPT_NAME, $node_id, friendly_bookmark($workshop));
    $name = $USER->full_name;
    $nick = $USER->username;
    $datim = gmstrftime('%Y-%m-%d %T');
    $hmac_key = $secret;
    $hmac_msg = $org . $uri . $name . $nick . $datim;
    $sig = hmac($hmac_key, $hmac_msg);
    $progcrew = $CFG->progwww_short . '/modules/crew';
    $css = $progcrew . '/' . $skin;
    if ($CFG->debug || !file_exists($CFG->progdir . '/modules/crew/crew.min.js')) {
        $js = $progcrew . '/crew.js';
    } else {
        $js = $progcrew . '/crew.min.js';
    }
    $theme->add_content(html_tag('script'));
    $theme->add_content(crew_screen($loc, $nick, $name, $uri, $workshop, $org, $datim, $sig, $css, $js, $progcrew));
    $theme->add_content(html_tag_close('script'));
    return TRUE;
}
 /** save the newly added area to the database
  *
  * This saves the essential information of a new area to the database,
  * using sensible defaults for the other fields. Also, a data directory
  * is created and the relative path is stored in the new area record.
  *
  * If something goes wrong, the user can redo the dialog, otherwise we
  * return to the area overview.
  *
  * @return void results are returned as output in $this->output
  * @uses $WAS_SCRIPT_NAME
  * @uses $CFG
  * @uses $USER
  */
 function area_savenew()
 {
     global $WAS_SCRIPT_NAME, $USER, $CFG;
     // 1 -- bail out if user pressed cancel button
     if (isset($_POST['button_cancel'])) {
         $this->output->add_message(t('cancelled', 'admin'));
         $this->area_overview();
         return;
     }
     // 2 -- dow we have permission to add an area?
     if (!$USER->has_site_permissions(PERMISSION_SITE_ADD_AREA)) {
         logger("areamanager: user attempted to add an area without permission");
         $msg = t('task_area_add_access_denied', 'admin');
         $this->output->add_message($msg);
         $this->output->add_popup_bottom($msg);
         $this->area_overview();
         return;
     }
     // 3 -- validate the data
     $invalid = FALSE;
     $dialogdef = $this->get_dialogdef_add_area();
     // 3A -- check for generic errors (string too short, number too small, etc)
     if (!dialog_validate($dialogdef)) {
         $invalid = TRUE;
     }
     // 3B -- additional check: valid datadirectory name entered
     $path = $dialogdef['area_path']['value'];
     $fname = isset($dialogdef['area_path']['label']) ? $dialogdef['area_path']['label'] : 'area_path';
     $params = array('{FIELD}' => str_replace('~', '', $fname));
     $areadata_directory = sanitise_filename($path);
     if ($path != $areadata_directory) {
         // User probably entered a few 'illegal' characters. This is no good
         $dialogdef['area_path']['value'] = $areadata_directory;
         // 'Help' user with a better proposition
         ++$dialogdef['area_path']['errors'];
         $params['{VALUE}'] = htmlspecialchars($path);
         $dialogdef['area_path']['error_messages'][] = t('validate_bad_filename', '', $params);
         $invalid = TRUE;
     }
     // 3C -- additional check: unique datadirectory name entered
     $areadata_directory = strtolower($areadata_directory);
     $where = array('path' => $areadata_directory);
     if (db_select_single_record('areas', 'area_id', $where) !== FALSE) {
         // Oops, a record with that path already exists. Go flag error
         ++$dialogdef['area_path']['errors'];
         $params['{VALUE}'] = $areadata_directory;
         $dialogdef['area_path']['error_messages'][] = t('validate_not_unique', '', $params);
         $invalid = TRUE;
     }
     // 3D -- additional check: can we create said directory?
     $areadata_full_path = $CFG->datadir . '/areas/' . $areadata_directory;
     $areadata_directory_created = @mkdir($areadata_full_path, 0700);
     if ($areadata_directory_created) {
         @touch($areadata_full_path . '/index.html');
         // "protect" the newly created directory from prying eyes
     } else {
         // Mmmm, failed; probably already exists then. Oh well. Go flag error.
         ++$dialogdef['area_path']['errors'];
         $params['{VALUE}'] = '/areas/' . $areadata_directory;
         $dialogdef['area_path']['error_messages'][] = t('validate_already_exists', '', $params);
         $invalid = TRUE;
     }
     // 3E -- if there were any errors go redo dialog while keeping data already entered
     if ($invalid) {
         if ($areadata_directory_created) {
             // Only get rid of the directory _we_ created
             @unlink($areadata_full_path . '/index.html');
             @rmdir($areadata_full_path);
         }
         // there were errors, show them to the user and do it again
         foreach ($dialogdef as $k => $item) {
             if (isset($item['errors']) && $item['errors'] > 0) {
                 $this->output->add_message($item['error_messages']);
             }
         }
         $this->output->add_content('<h2>' . t('areamanager_add_area_header', 'admin') . '</h2>');
         $this->output->add_content(t('areamanager_add_area_explanation', 'admin'));
         $href = href($WAS_SCRIPT_NAME, $this->a_param(AREAMANAGER_CHORE_SAVE_NEW));
         $this->output->add_content(dialog_quickform($href, $dialogdef));
         return;
     }
     // 4 -- go save the new area
     $sort_order = $this->sort_order_new_area();
     $now = strftime('%Y-%m-%d %T');
     $theme_id = intval($dialogdef['area_theme_id']['value']);
     $area_title = $dialogdef['area_title']['value'];
     $fields = array('title' => $area_title, 'is_private' => $dialogdef['area_is_private']['value'] == 1 ? TRUE : FALSE, 'is_active' => TRUE, 'is_default' => FALSE, 'path' => $areadata_directory, 'metadata' => '', 'sort_order' => $sort_order, 'theme_id' => $theme_id, 'ctime' => $now, 'cuser_id' => $USER->user_id, 'mtime' => $now, 'muser_id' => $USER->user_id);
     // 4A -- store area data
     $success = TRUE;
     $new_area_id = db_insert_into_and_get_id('areas', $fields, 'area_id');
     if ($new_area_id === FALSE) {
         if ($areadata_directory_created) {
             // Only get rid of the directory _we_ created
             @unlink($areadata_full_path . '/index.html');
             @rmdir($areadata_full_path);
         }
         logger("areamanager: saving new area failed: " . db_errormessage());
         $success = FALSE;
     }
     // 4B -- handle theme settings for this area
     if ($success) {
         if (!$this->reset_theme_defaults($new_area_id, $theme_id)) {
             logger("areamanager: saving new area-theme properties failed: " . db_errormessage());
             $success = FALSE;
         }
     }
     // 5 -- tell user about results of the operation
     if ($success) {
         $params = array('{AREA}' => $new_area_id, '{AREA_FULL_NAME}' => $area_title);
         $this->output->add_message(t('areamanager_savenew_area_success', 'admin', $params));
         logger(sprintf("areamanager: success saving new area '%d' %s with data directory /areas/%s", $new_area_id, $area_title, $areadata_directory));
     } else {
         $this->output->add_message(t('areamanager_savenew_area_failure', 'admin'));
     }
     $this->area_overview();
 }
 /** save the modified configuration parameters to the database
  *
  * @param object &$output the object that collects the output
  * @result FALSE on error + error messages added to messages part of output or TRUE and data stored to database.
  * @uses dialog_validate()
  */
 function save_data(&$output)
 {
     if (empty($this->dialogdef)) {
         $this->dialogdef = $this->get_dialogdef();
     }
     if (!dialog_validate($this->dialogdef)) {
         foreach ($this->dialogdef as $k => $item) {
             if (isset($item['errors']) && $item['errors'] > 0) {
                 $output->add_message($item['error_messages']);
             }
         }
         return FALSE;
     }
     $errors = 0;
     $records_changed = 0;
     $records_unchanged = 0;
     if (is_array($this->records)) {
         foreach ($this->records as $id => $record) {
             $name = $this->prefix . 'record_' . strval($id);
             if (isset($this->dialogdef[$name])) {
                 if ($this->dialogdef[$name]['value'] != $this->dialogdef[$name]['old_value']) {
                     ++$records_changed;
                     $fields = array('value' => strval($this->dialogdef[$name]['value']));
                     $where = array($this->keyfield => $id);
                     if (db_update($this->table, $fields, $where) === FALSE) {
                         logger('configassistant: error saving config value: ' . db_errormessage());
                         ++$errors;
                     } else {
                         logger(sprintf("configassistant: success updating %s[%s] => '%s'", $this->table, $id, strval($this->dialogdef[$name]['value'])), WLOG_DEBUG);
                     }
                 } else {
                     ++$records_unchanged;
                 }
                 // if (modified)
             }
             // if (data available)
         }
         // foreach
     }
     // if (records)
     logger(sprintf('configassistant: save configuration in table %s: unchanged: %d, changed: %d, errors: %d', $this->table, $records_unchanged, $records_changed, $errors), WLOG_DEBUG);
     if ($errors == 0) {
         $output->add_message(t('success_saving_data', 'admin'));
         $retval = TRUE;
     } else {
         $output->add_message(t('errors_saving_data', 'admin', array('{ERRORS}' => strval($errors))));
         $retval = FALSE;
     }
     return $retval;
 }
 /** create a new subdirectory
  *
  * This routine either shows a dialog where the user can specify the name of a new
  * directory to add OR processes the dialog.
  *
  * In case of directory name too short, already exists, etc. the user is
  * returned to the dialog to try again. If all goes well the new directory
  * is created and at the same time the empty file 'index.html' is created
  * to "protect" the directory from prying eyes.
  *
  * @return void output returned via $this->output
  */
 function task_add_directory()
 {
     global $WAS_SCRIPT_NAME, $CFG;
     // 1A -- bail out if user pressed cancel button
     if (isset($_POST['button_cancel'])) {
         $this->output->add_message(t('cancelled', 'admin'));
         $this->task_list_directory();
         return;
     }
     // 1B -- Check validity of working directory, maybe bail out
     $newdir = get_parameter_string(PARAM_PATH, $this->current_directory);
     if (($path = $this->valid_path($newdir)) === FALSE) {
         $this->output->add_message(t('invalid_path', 'admin', array('{PATH}' => htmlspecialchars($newdir))));
         $this->task_list_directory();
         return;
     }
     $this->current_directory = $path;
     // this is where we will create the new subdirectory
     // 2 -- prepare dialog (either to show it or to validate it)
     $dialogdef = array('subdirectory' => array('type' => F_ALPHANUMERIC, 'name' => 'subdirectory', 'minlength' => 1, 'maxlength' => 240, 'columns' => 30, 'label' => t('filemanager_add_subdirectory_label', 'admin'), 'title' => t('filemanager_add_subdirectory_title', 'admin'), 'value' => ''), 'button_save' => dialog_buttondef(BUTTON_SAVE), 'button_cancel' => dialog_buttondef(BUTTON_CANCEL));
     $a_params = array('job' => $this->job, 'task' => TASK_ADD_DIRECTORY, PARAM_PATH => $path);
     $href = href($WAS_SCRIPT_NAME, $a_params);
     // 3 -- show dialog or validate + process?
     if (!isset($_POST['subdirectory'])) {
         $this->output->add_content('<h2>' . t('filemanager_add_subdirectory_header', 'admin') . '</h2>');
         $this->output->add_content(t('filemanager_add_subdirectory_explanation', 'admin'));
         $this->output->add_content(dialog_quickform($href, $dialogdef));
         $this->show_breadcrumbs($path);
         $this->show_menu($path);
         return;
     }
     // 4 -- validate user input and maybe create directory
     // 4A -- check for generic errors
     $invalid = FALSE;
     if (!dialog_validate($dialogdef)) {
         $invalid = TRUE;
     }
     // 4B -- check for additional errors: sane filename
     $subdirectory = $dialogdef['subdirectory']['value'];
     $sanitised_subdirectory = sanitise_filename($subdirectory);
     if ($subdirectory != $sanitised_subdirectory || substr($sanitised_subdirectory, 0, strlen(THUMBNAIL_PREFIX)) == THUMBNAIL_PREFIX) {
         ++$dialogdef['subdirectory']['errors'];
         $params = array('{FIELD}' => str_replace('~', '', $dialogdef['subdirectory']['label']), '{VALUE}' => htmlspecialchars($subdirectory));
         $dialogdef['subdirectory']['error_messages'][] = t('validate_bad_filename', '', $params);
         $invalid = TRUE;
     }
     // 4C -- check for additional errors: directory should not exist already
     $subdirectory_full_path = $CFG->datadir . $path . '/' . $sanitised_subdirectory;
     if (file_exists($subdirectory_full_path)) {
         ++$dialogdef['subdirectory']['errors'];
         $params = array('{FIELD}' => str_replace('~', '', $dialogdef['subdirectory']['label']), '{VALUE}' => $this->vpath($path) . '/' . $sanitised_subdirectory);
         $dialogdef['subdirectory']['error_messages'][] = t('validate_already_exists', '', $params);
         $invalid = TRUE;
     }
     // 4D -- redo dialog if there were errors
     if ($invalid) {
         // shortcut: only the subdirectory field can (and does) yield errors
         $this->output->add_message($dialogdef['subdirectory']['error_messages']);
         $dialogdef['subdirectory']['value'] = $sanitised_subdirectory;
         $this->output->add_content('<h2>' . t('filemanager_add_subdirectory_header', 'admin') . '</h2>');
         $this->output->add_content(t('filemanager_add_subdirectory_explanation', 'admin'));
         $this->output->add_content(dialog_quickform($href, $dialogdef));
         $this->show_breadcrumbs($path);
         // note that we do NOT display the menu here; let the user concentrate (or press Cancel) this time
         return;
     }
     // 5 -- all set, go create subdir
     $params = array('{PATH}' => htmlspecialchars($this->vpath($path)), '{DIRECTORY}' => htmlspecialchars($sanitised_subdirectory));
     if (@mkdir($subdirectory_full_path, 0700)) {
         @touch($subdirectory_full_path . '/index.html');
         // "protect" the newly created directory from prying eyes
         $this->output->add_message(t('filemanager_add_subdirectory_success', 'admin', $params));
         logger(sprintf('%s.%s(): success with mkdir %s', __CLASS__, __FUNCTION__, $path . '/' . $subdirectory), WLOG_DEBUG);
     } else {
         $this->output->add_message(t('filemanager_add_subdirectory_failure', 'admin', $params));
         logger(sprintf('%s.%s(): cannot mkdir %s', __CLASS__, __FUNCTION__, $path . '/' . $subdirectory));
     }
     $this->task_list_directory();
 }
 /** workhorse routing for saving modified node data to the database
  *
  * this is the 'meat' in saving the modified node data. There are a lot
  * of complicated things we need to take care of, including dealing with
  * the readonly property (if a node is currently readonly, nothing should
  * be changed whatsoever, except removing the readonly attribute) and with
  * moving a non-empty section to another area. Especially the latter is not
  * trivial to do, therefore it is being done in a separate routine
  * (see {@link save_node_new_area_mass_move()}).
  *
  * Note that we need to return the user to the edit dialog if the data
  * entered is somehow incorrect. If everything is OK, we simply display the
  * treeview and the area menu, as usual.
  *
  * Another complication is dealing with a changed module. If the user decides
  * to change the module, we need to inform the old module that it is no longer
  * connected to this page and is effectively 'deleted'. Subsequently we have to
  * tell the new module that it is in fact now added to this node. It is up to
  * the module's code to deal with these removals and additions (for some 
  * modules it could boil down to a no-op).
  *
  * Finally there is a complication with parent nodes and sort order.
  * The sort order is specified by the user via selecting the node AFTER which
  * this node should be positioned. However, this list of nodes is created
  * based on the OLD parent of the node. If the node is moved to elsewhere in
  * the tree, sorting after a node in another branch no longer makes sense.
  * Therefore, if both the parent and the sort order are changed, the parent
  * prevails (and the sort order information is discarded).
  *
  * @param int $node_id the node we have to change
  * @todo this routine could be improved by refactoring it; it is too long!
  * @return void results are returned as output in $this->output
  * @todo there is something wrong with embargo: should we check starting at parent or at node?
  *       this is not clear: it depends on basic/advanced and whether the embargo field changed.
  *       mmmm... safe choice: start at node_id for the time being
  */
 function save_node($node_id)
 {
     global $USER, $WAS_SCRIPT_NAME;
     // 0 -- prepare some useful information
     $is_advanced = intval($_POST['dialog']) == DIALOG_NODE_EDIT_ADVANCED ? TRUE : FALSE;
     $is_page = db_bool_is(TRUE, $this->tree[$node_id]['record']['is_page']);
     $viewonly = db_bool_is(TRUE, $this->tree[$node_id]['record']['is_readonly']);
     $embargo = is_under_embargo($this->tree, $node_id);
     // 1 -- perhaps make node read-write again + quit
     if ($viewonly) {
         $anode = array('{NODE_FULL_NAME}' => $this->node_full_name($node_id));
         if ($is_advanced && !isset($_POST['node_is_readonly'])) {
             $fields = array('is_readonly' => FALSE);
             $where = array('node_id' => $node_id);
             $retval = db_update('nodes', $fields, $where);
             logger(sprintf(__CLASS__ . ': set node %s%s to readwrite: %s', $this->node_full_name($node_id), $embargo ? ' (embargo)' : '', $retval === FALSE ? 'failed: ' . db_errormessage() : 'success'));
             $message = t('node_no_longer_readonly', 'admin', $anode);
             $this->output->add_message($message);
             if (!$embargo) {
                 $nodes = $this->get_node_id_and_ancestors($node_id);
                 $this->queue_area_node_alert($this->area_id, $nodes, $message, $USER->full_name);
             }
             // update our cached version of this node, saving a trip to the database
             $this->tree[$node_id]['record']['is_readonly'] = SQL_FALSE;
         } else {
             $this->output->add_message(t('node_still_readonly', 'admin', $anode));
         }
         lock_release_node($node_id);
         $this->show_tree();
         $this->show_area_menu($this->area_id);
         return;
     }
     // 2 -- validate data
     $dialogdef = $is_advanced ? $this->get_dialogdef_edit_advanced_node($node_id, $is_page, $viewonly) : $this->get_dialogdef_edit_node($node_id, $is_page, $viewonly);
     if (!dialog_validate($dialogdef)) {
         // errors? show them to the user and edit again
         foreach ($dialogdef as $k => $item) {
             if (isset($item['errors']) && $item['errors'] > 0) {
                 $this->output->add_message($item['error_messages']);
             }
         }
         $anode = array('{NODE}' => strval($node_id), '{NODE_FULL_NAME}' => $this->node_full_name($node_id));
         if ($is_advanced) {
             $title = t($is_page ? 'edit_a_page_advanced_header' : 'edit_a_section_advanced_header', 'admin', $anode);
             $expl = t($is_page ? 'edit_page_advanced_explanation' : 'edit_section_advanced_explanation', 'admin', $anode);
         } else {
             $title = t($is_page ? 'edit_a_page_header' : 'edit_a_section_header', 'admin', $anode);
             $expl = t($is_page ? 'edit_page_explanation' : 'edit_section_explanation', 'admin', $anode);
         }
         $this->output->add_content('<h2>' . $title . '</h2>');
         $this->output->add_content($expl);
         $href = href($WAS_SCRIPT_NAME, array('job' => JOB_PAGEMANAGER, 'task' => TASK_SAVE_NODE, 'node' => $node_id));
         $this->output->add_content(dialog_quickform($href, $dialogdef));
         $this->output->set_funnel_mode(TRUE);
         // no distractions
         // Note that we also do NOT show the edit menu: we try to let the user concentrate
         // on the task at hand;  the only escape route is 'Cancel'...
         // Also note that we still have the record lock; that won't change because we
         // will be editing the page again. Cancel'ing will also release the lock.
         return;
     }
     // 3A -- prepare for update of node record - phase 1
     $now = strftime("%Y-%m-%d %T");
     $fields = array('mtime' => $now);
     $changed_parent = FALSE;
     $changed_sortorder = FALSE;
     $changed_module = FALSE;
     $changed_area = FALSE;
     $new_area_mass_move = FALSE;
     foreach ($dialogdef as $name => $item) {
         if (isset($item['viewonly']) && $item['viewonly']) {
             continue;
         }
         switch ($name) {
             // basic fields
             case 'node_title':
                 $fields['title'] = $item['value'];
                 $this->tree[$node_id]['record']['title'] = $item['value'];
                 // for full_name in message below
                 break;
             case 'node_link_text':
                 $fields['link_text'] = $item['value'];
                 $this->tree[$node_id]['record']['link_text'] = $item['value'];
                 // for full_name in message below
                 break;
             case 'node_parent_id':
                 if ($this->tree[$node_id]['parent_id'] != $item['value']) {
                     $parent_id = intval($item['value']);
                     // could be 0, indicating top level node (see below)
                     $changed_parent = TRUE;
                 }
                 break;
             case 'node_module_id':
                 if ($this->tree[$node_id]['record']['module_id'] != $item['value']) {
                     $fields['module_id'] = intval($item['value']);
                     $changed_module = TRUE;
                 }
                 break;
             case 'node_sort_after_id':
                 if ($this->tree[$node_id]['prev_sibling_id'] != $item['value']) {
                     $node_sort_after_id = $item['value'];
                     // deal with this after this foreach()
                     $changed_sortorder = TRUE;
                 }
                 break;
                 // advanced fields
             // advanced fields
             case 'node_area_id':
                 $new_area_id = intval($item['value']);
                 if ($this->tree[$node_id]['record']['area_id'] != $new_area_id) {
                     if ($is_page || $this->tree[$node_id]['first_child_id'] == 0) {
                         $fields['area_id'] = intval($item['value']);
                         $changed_area = TRUE;
                     } else {
                         $new_area_mass_move = TRUE;
                     }
                 }
                 break;
             case 'node_link_image':
                 $fields['link_image'] = $item['value'];
                 break;
             case 'node_link_image_width':
                 $fields['link_image_width'] = intval($item['value']);
                 break;
             case 'node_link_image_height':
                 $fields['link_image_height'] = intval($item['value']);
                 break;
             case 'node_is_hidden':
                 $fields['is_hidden'] = $item['value'] == 1 ? TRUE : FALSE;
                 break;
             case 'node_is_readonly':
                 $fields['is_readonly'] = $item['value'] == 1 ? TRUE : FALSE;
                 break;
             case 'node_embargo':
                 $fields['embargo'] = $item['value'];
                 if ($now < $fields['embargo']) {
                     $embargo = TRUE;
                 }
                 break;
             case 'node_expiry':
                 $fields['expiry'] = $item['value'];
                 break;
             case 'node_style':
                 $fields['style'] = $item['value'];
                 break;
         }
         // switch
     }
     // foreach
     // 3B -- prepare for update - phase 2 ('simple exceptions')
     if ($changed_area) {
         // a single node will be moved to the top level of the new area
         $parent_id = 0;
         $newtree = tree_build($new_area_id);
         $fields['sort_order'] = $this->calculate_new_sort_order($newtree, $new_area_id, $parent_id);
         unset($newtree);
         $fields['parent_id'] = $node_id;
         // $parent_id == $node_id means: top level
         $fields['is_default'] = FALSE;
         // otherwise the target section might end up with TWO defaults...
     } elseif ($changed_parent) {
         // the node will be moved to another section (or the top level)
         $fields['sort_order'] = $this->calculate_new_sort_order($this->tree, $this->area_id, $parent_id);
         $fields['parent_id'] = $parent_id == 0 ? $node_id : $parent_id;
         $fields['is_default'] = FALSE;
         // otherwise the target section might end up with TWO defaults...
     } elseif ($changed_sortorder) {
         // simply change the sort order
         $fields['sort_order'] = $this->calculate_updated_sort_order($node_id, $node_sort_after_id);
     }
     // 4A -- actually update the database for the pending 'simple' changes
     $errors = 0;
     if ($changed_module) {
         if (!$this->module_disconnect($this->area_id, $node_id, $this->tree[$node_id]['record']['module_id'])) {
             ++$errors;
         }
     }
     $where = array('node_id' => $node_id);
     if (db_update('nodes', $fields, $where) === FALSE) {
         logger(sprintf('%s.%s(): error saving node \'%d\'%s: %s', __CLASS__, __FUNCTION__, $node_id, $embargo ? ' (embargo)' : '', db_errormessage()));
         ++$errors;
     }
     if ($changed_module) {
         if (!$this->module_connect($this->area_id, $node_id, $fields['module_id'])) {
             ++$errors;
         }
     }
     if ($errors == 0) {
         logger(sprintf('%s.%s(): success saving node \'%d\'%s', __CLASS__, __FUNCTION__, $node_id, $embargo ? ' (embargo)' : ''), WLOG_DEBUG);
         $anode = array('{AREA}' => $this->area_id, '{NODE_FULL_NAME}' => $this->node_full_name($node_id));
         $this->output->add_message(t($is_page ? 'page_saved' : 'section_saved', 'admin', $anode));
         if (!$embargo) {
             $nodes = $this->get_node_id_and_ancestors($node_id);
             if ($changed_area) {
                 $areas = array($this->area_id, $fields['area_id']);
                 $anode['{NEWAREA}'] = $fields['area_id'];
                 $message = t('node_was_edited_and_moved', 'admin', $anode);
             } else {
                 $areas = $this->area_id;
                 $message = t('node_was_edited', 'admin', $anode);
             }
             $this->queue_area_node_alert($areas, $nodes, $message, $USER->full_name);
         }
     } else {
         $message = t('error_saving_node', 'admin');
         $this->output->add_message($message);
     }
     // 4B -- update the database for the special case of a mass-move
     if ($new_area_mass_move) {
         $this->save_node_new_area_mass_move($node_id, $new_area_id, $embargo);
     }
     // 5 -- clean up + show updated and re-read tree again
     lock_release_node($node_id);
     $this->build_cached_tree($this->area_id, TRUE);
     // force re-read of the tree structure
     $this->show_tree();
     $this->show_area_menu($this->area_id);
 }
 /** save changed job permissions to the database
  *
  * this saves the changed job permissions to the acls table.
  *
  * If the user selected the guru option, we simply set the permissions to
  * JOB_PERMISSION_GURU (i.e. all permissions set). If not, we iterate through
  * all existing permissions and set the corresponding bits.
  * After that the data is saved to the correct acls-record.
  *
  * @todo fix the crude error check on dialogdef === FALSE here
  * @return bool FALSE on error, TRUE otherwise
  */
 function save_data_admin()
 {
     if (is_null($this->dialogdef)) {
         $this->dialogdef = $this->get_dialogdef_admin($this->acl_id, $this->related_acls);
     }
     if ($this->dialogdef === FALSE) {
         return FALSE;
         // shouldn't happen...
     }
     if (!dialog_validate($this->dialogdef)) {
         // there were errors, show them to the user return error
         foreach ($this->dialogdef as $k => $item) {
             if (isset($item['errors']) && $item['errors'] > 0) {
                 $this->output->add_message($item['error_messages']);
             }
         }
         return FALSE;
     }
     // At this point we have valid values in $this->dialogdef
     // Also, we have the old values in our hands so we can sense the difference
     // Job permissions are a special case because we manipulate the bits here.
     // Strategy:
     // If they want to set guru-permissions => set $jobs to -1 and we're done
     // If not, iterate throug the individual bits
     // Since the dialogdef is keyed with the fieldnames we can directly
     // access the interesting fields. Here we go.
     if ($this->dialogdef['job_guru']['value'] == 1) {
         $permissions_jobs = JOB_PERMISSION_GURU;
     } else {
         $permissions_jobs = 0;
         for ($job = 1; $job < JOB_PERMISSION_NEXT_AVAILABLE_VALUE; $job <<= 1) {
             if ($this->dialogdef['job_' . strval($job)]['value'] == 1) {
                 $permissions_jobs |= $job;
             }
         }
     }
     $acl_id = intval($this->acl_id);
     $rows = db_update('acls', array('permissions_jobs' => $permissions_jobs), array('acl_id' => $acl_id));
     if ($rows !== FALSE) {
         $this->output->add_message(t('success_saving_data', 'admin'));
         $retval = TRUE;
     } else {
         $this->output->add_message(t('errors_saving_data', 'admin', array('{ERRORS}' => 1)));
         logger(sprintf("aclmanager: error updating job permissions for acl_id='%d': %s", $acl_id, db_errormessage()));
         $retval = FALSE;
     }
     return $retval;
 }
/** save the modified content data of this module linked to node $node_id
 *
 * this validates and saves the data that was submitted by the user.
 * If validation fails, or storing the data doesn't work, the flag $edit_again
 * is set to TRUE and the return value is FALSE.
 *
 * If the user has cancelled the operation, the flag $edit_again is set to FALSE
 * and the return value is also FALSE.
 *
 * If the modified data is stored successfully, the return value is TRUE (and
 * the value of $edit_again is a don't care).
 *
 * Here is a summary of return values.
 *
 *  - retval = TRUE ==> data saved successfully
 *  - retval = FALSE && edit_again = TRUE ==> re-edit the data, show the edit dialog again
 *  - retval = FALSE && edit_again = FALSE ==> cancelled, do nothing
 *
 * @param object &$output collects the html output (if any)
 * @param int $area_id the area in which $node_id resides
 * @param int $node_id the node to which the content is connected
 * @param array $module the module record straight from the database
 * @param bool $viewonly if TRUE, editing and hence saving is not allowed
 * @param bool &$edit_again set to TRUE if we need to edit the content again, FALSE otherwise
 * @return bool TRUE on success + output stored via $output, FALSE otherwise
 */
function redirect_save(&$output, $area_id, $node_id, $module, $viewonly, &$edit_again)
{
    global $USER;
    // 1 -- bail out if cancelled or viewonly
    if (isset($_POST['button_cancel']) || $viewonly) {
        $edit_again = FALSE;
        return FALSE;
    }
    // 2 -- redo if invalid data was submitted
    $dialogdef = redirect_get_dialogdef($viewonly);
    if (!dialog_validate($dialogdef)) {
        // there were errors, show them to the user and do it again
        foreach ($dialogdef as $k => $item) {
            if (isset($item['errors']) && $item['errors'] > 0) {
                $output->add_message($item['error_messages']);
            }
        }
        $edit_again = TRUE;
        return FALSE;
    }
    // 3 -- actually save the new setting
    $now = strftime('%Y-%m-%d %T');
    $table = 'nodes';
    $fields = array('link_href' => trim($dialogdef['link_href']['value']), 'link_target' => trim($dialogdef['link_target']['value']), 'mtime' => $now);
    $where = array('node_id' => intval($node_id));
    $retval = db_update($table, $fields, $where);
    if (db_update($table, $fields, $where) === FALSE) {
        logger(sprintf('%s(): error saving config value: %s', __FUNCTION__, db_errormessage()));
        $edit_again = TRUE;
        return FALSE;
    } else {
        return TRUE;
        // $edit_again is a don't care
    }
}
/** validate the data entered by the user
 *
 * Other than the standard validation we check for at least 1 destination
 *
 * @param object &$dialogdef defines the dialog and will hold POSTed values
 * @param int $node_id which page?
 * @param array $addresses holds the currently defined addresses
 * @return bool TRUE if valid, else FALSE + messages added to dialogdef
 */
function mailpage_dialog_validate(&$dialogdef, $node_id, $addresses)
{
    $retval = TRUE;
    // assume success
    // 1 -- check the usual suspects (side-effect: fetch POSTed values in dialogdef)
    if (!dialog_validate($dialogdef)) {
        $retval = FALSE;
    }
    // 2 -- special case: there must be at least 1 destination
    if (sizeof($addresses) < 1) {
        $retval = FALSE;
        ++$dialogdef['button_save']['errors'];
        $params = array('{NODE}' => $node_id);
        $dialogdef['button_save']['error_messages'][] = t('error_retrieving_addresses', 'm_mailpage', $params);
    }
    return $retval;
}
 /** save an edited group to the database, including adding/modifying/deleting group/capacity-records
  *
  * Note: no error checking when inserting new capacity because we more or less know
  * that that capacity does not exist already or it would have been in the array already.
  * (But what if there are more than GROUPMANAGER_MAX_CAPACITIES in the database? Mmmm....
  *
  * @output void work done and output via $this->output
  * @uses $WAS_SCRIPT_NAME;
  */
 function group_save()
 {
     global $WAS_SCRIPT_NAME, $USER;
     // 0 -- sanity check
     $group_id = get_parameter_int('group', NULL);
     if (is_null($group_id)) {
         logger(sprintf("%s.%s(): unspecified parameter group", __CLASS__, __FUNCTION__));
         $this->output->add_message(t('error_invalid_parameters', 'admin'));
         $this->groups_overview();
         return;
     }
     // 1 -- bail out if the user pressed cancel button
     if (isset($_POST['button_cancel'])) {
         $this->output->add_message(t('cancelled', 'admin'));
         $this->groups_overview();
         return;
     }
     // 2 -- validate the data
     $dialogdef = $this->get_dialogdef_edit_group($group_id);
     if ($dialogdef === FALSE) {
         $this->output->add_message(t('error_retrieving_data', 'admin'));
         $this->groups_overview();
         return;
     }
     // 2A -- remember generic errors (string too short, number too small, etc)
     $invalid = FALSE;
     if (!dialog_validate($dialogdef)) {
         $invalid = TRUE;
     }
     // 2B -- check out the groupname: this field should be unique
     $record = db_select_single_record('groups', 'group_id', array('groupname' => $dialogdef['group_name']['value']));
     if ($record !== FALSE && intval($record['group_id']) != $group_id) {
         // Another group exists with this name
         ++$dialogdef['group_name']['errors'];
         $fname = isset($dialogdef['group_name']['label']) ? str_replace('~', '', $dialogdef['group_name']['label']) : $dialogdef['group_name']['name'];
         $dialogdef['group_name']['error_messages'][] = t('validate_not_unique', '', array('{FIELD}' => $fname));
         $invalid = TRUE;
     }
     // 2C -- watch out for ACLs marked for deletion that are related to this $USER
     $new = array();
     for ($i = 1; $i <= GROUPMANAGER_MAX_CAPACITIES; ++$i) {
         if (($capacity_code = intval($dialogdef['group_capacity_' . $i]['value'])) != CAPACITY_NONE) {
             $new[$capacity_code] = $capacity_code;
         }
     }
     for ($i = 1; $i <= GROUPMANAGER_MAX_CAPACITIES; ++$i) {
         $k = 'group_capacity_' . $i;
         if (($capacity_code = intval($dialogdef[$k]['old_value'])) != CAPACITY_NONE && !isset($new[$capacity_code])) {
             // this existing capacity code is marked for deletion
             $acl_id = intval($dialogdef[$k]['acl_id']);
             if (isset($USER->related_acls[$acl_id])) {
                 // Houston we have a problem: user's membership at stake
                 ++$dialogdef[$k]['errors'];
                 $params = $this->get_group_capacity_names($group_id, $capacity_code);
                 $params['{FIELD}'] = isset($dialogdef[$k]['label']) ? str_replace('~', '', $dialogdef[$k]['label']) : $dialogdef[$k]['name'];
                 $dialogdef[$k]['error_messages'][] = t('usermanager_delete_group_capacity_not_self', 'admin', $params);
                 $invalid = TRUE;
             }
         }
     }
     // 2D -- if there were any errors go redo dialog while keeping data already entered
     if ($invalid) {
         foreach ($dialogdef as $k => $item) {
             if (isset($item['errors']) && $item['errors'] > 0) {
                 $this->output->add_message($item['error_messages']);
             }
         }
         $this->output->add_content('<h2>' . t('groupmanager_edit_group_header', 'admin') . '</h2>');
         $this->output->add_content(t('groupmanager_edit_group_explanation', 'admin'));
         $href = href($WAS_SCRIPT_NAME, $this->a_params(TASK_GROUP_SAVE, $group_id));
         $this->output->add_content(dialog_quickform($href, $dialogdef));
         // note: we suppress the distracting menu this time: user should focus on entering correct data
         // However, we do show the breadcrumb trail
         $this->show_breadcrumbs_group();
         $this->output->add_breadcrumb($WAS_SCRIPT_NAME, $this->a_params(TASK_GROUP_EDIT, $group_id), array('title' => t('groupmanager_group_menu_edit_title', 'admin')), t('groupmanager_group_menu_edit', 'admin'));
         return;
     }
     // 3 -- store update group data
     $errors = 0;
     // 3A -- maybe update group record in table 'groups'
     $fields = array();
     if ($dialogdef['group_name']['value'] != $dialogdef['group_name']['old_value']) {
         $fields['groupname'] = $dialogdef['group_name']['value'];
     }
     if ($dialogdef['group_fullname']['value'] != $dialogdef['group_fullname']['old_value']) {
         $fields['full_name'] = $dialogdef['group_fullname']['value'];
     }
     if ($dialogdef['group_is_active']['value'] != $dialogdef['group_is_active']['old_value']) {
         $fields['is_active'] = $dialogdef['group_is_active']['value'] == 1 ? TRUE : FALSE;
     }
     $table = 'groups';
     if (sizeof($fields) > 0) {
         $where = array('group_id' => $group_id);
         if (db_update($table, $fields, $where) === FALSE) {
             ++$errors;
             logger(sprintf("%s.%s(): error saving data group '%d' (%s) in table '%s': %s", __CLASS__, __FUNCTION__, $group_id, $dialogdef['group_name']['value'], $table, db_errormessage()));
         } else {
             logger(sprintf("%s.%s(): success saving changes to group '%d' (%s) in table '%s'", __CLASS__, __FUNCTION__, $group_id, $dialogdef['group_name']['value'], $table), WLOG_DEBUG);
         }
     } else {
         logger(sprintf("%s.%s(): no changes to save for group '%d' (%s) in table '%s'", __CLASS__, __FUNCTION__, $group_id, $dialogdef['group_name']['value'], $table), WLOG_DEBUG);
     }
     // 4 -- maybe add, change or delete child records for this group (ie. links to capacities etc.)
     // 4A -- collect the lists of old capacities (with sort order and acl) and new capacities (with new sort order)
     $new = array();
     $old = array();
     $sort_order_new = 0;
     for ($i = 1; $i <= GROUPMANAGER_MAX_CAPACITIES; ++$i) {
         $k = 'group_capacity_' . $i;
         if (($capacity_code = intval($dialogdef[$k]['value'])) != CAPACITY_NONE && !isset($new[$capacity_code])) {
             $new[$capacity_code] = ++$sort_order_new;
         }
         if (($capacity_code = intval($dialogdef[$k]['old_value'])) != CAPACITY_NONE) {
             $old[$capacity_code] = array('sort_order' => intval($dialogdef[$k]['sort_order']), 'acl_id' => intval($dialogdef[$k]['acl_id']));
         }
     }
     // 4B -- handle deletions and updates
     foreach ($old as $capacity_code => $v) {
         $acl_id = $v['acl_id'];
         $sort_order = $v['sort_order'];
         $where_group_capacity = array('group_id' => $group_id, 'capacity_code' => $capacity_code);
         if (!isset($new[$capacity_code])) {
             //
             // 4B1 -- get rid of this group/capacity and acl (in correct order due to FK constraints)
             $where_acl_id = array('acl_id' => $acl_id);
             $table_wheres = array('acls_areas' => $where_acl_id, 'acls_nodes' => $where_acl_id, 'acls_modules' => $where_acl_id, 'acls_modules_areas' => $where_acl_id, 'acls_modules_nodes' => $where_acl_id, 'users_groups_capacities' => $where_group_capacity, 'groups_capacities' => $where_group_capacity, 'acls' => $where_acl_id);
             $message = sprintf("%s.%s(): del group/capacity=%d/%d:", __CLASS__, __FUNCTION__, $group_id, $capacity_code);
             foreach ($table_wheres as $table => $where) {
                 if (($rowcount = db_delete($table, $where)) === FALSE) {
                     ++$errors;
                     logger(sprintf("%s.%s(): delete from '%s' failed: %s", __CLASS__, __FUNCTION__, $table, db_errormessage()));
                 } else {
                     $message .= sprintf(" '%s':%d", $table, $rowcount);
                 }
             }
             logger($message, WLOG_DEBUG);
         } elseif ($sort_order != $new[$capacity_code]) {
             //
             // 4B2 -- save changed sort order of this group/capacity
             $fields = array('sort_order' => $new[$capacity_code]);
             if (db_update('groups_capacities', $fields, $where_group_capacity) === FALSE) {
                 ++$errors;
                 logger(sprintf("%s.%s(): cannot update sort order in  group/capacity '%d/%d': %s", __CLASS__, __FUNCTION__, $group_id, $capacity_code, db_errormessage()));
             } else {
                 logger(sprintf("%s.%s(): success changing sort order '%d' -> '%d' for group/capacity '%d/%d'", __CLASS__, __FUNCTION__, $sort_order, $new[$capacity_code], $group_id, $capacity_code), WLOG_DEBUG);
             }
         }
         //  else
         // no changes
     }
     // 4C -- add new group/capacities when necessary
     foreach ($new as $capacity_code => $sort_order) {
         if (!isset($old[$capacity_code])) {
             if ($this->add_group_capacity($group_id, $capacity_code, $sort_order) === FALSE) {
                 ++$errors;
             }
         }
     }
     // 5 -- all done
     $params = array('{GROUP}' => $dialogdef['group_name']['value'], '{GROUP_FULL_NAME}' => $dialogdef['group_name']['value'], '{ERRORS}' => $errors);
     if ($errors == 0) {
         $this->output->add_message(t('groupmanager_edit_group_success', 'admin', $params));
     } else {
         $this->output->add_message(t('errors_saving_data', 'admin', $params));
     }
     logger(sprintf("%s.%s(): %s saving group '%d' '%s' (%s)", __CLASS__, __FUNCTION__, $errors == 0 ? "success" : "failure", $group_id, $params['{GROUP}'], $params['{GROUP_FULL_NAME}']));
     $this->groups_overview();
     return;
 }
 /** save the new group/capacity for the selected user
  *
  * this adds a record to the users_groups_capacities table, indicating
  * the group membership and the corresponding capacity for the user.
  *
  * @return void output written to browser via $this->output
  * @uses $WAS_SCRIPT_NAME
  */
 function user_groupsave()
 {
     global $WAS_SCRIPT_NAME;
     //
     // 0 -- sanity check
     //
     $user_id = get_parameter_int('user', NULL);
     if (is_null($user_id)) {
         logger("usermanager->user_groupsave(): unspecified parameter user");
         $this->output->add_message(t('error_invalid_parameters', 'admin'));
         $this->users_overview();
         return;
     }
     //
     // 1 -- bail out if the user pressed cancel button
     //
     if (isset($_POST['button_cancel'])) {
         $this->output->add_message(t('cancelled', 'admin'));
         $this->user_groups();
         return;
     }
     //
     // 2 -- make sure the data is valid
     //
     $dialogdef = $this->get_dialogdef_add_usergroup($user_id);
     if (!dialog_validate($dialogdef)) {
         foreach ($dialogdef as $k => $item) {
             if (isset($item['errors']) && $item['errors'] > 0) {
                 $this->output->add_message($item['error_messages']);
             }
         }
         $params = $this->get_user_names($user_id);
         $this->output->add_content('<h2>' . t('usermanager_user_groupadd_header', 'admin', $params) . '</h2>');
         $this->output->add_content(t('usermanager_user_groupadd_explanation', 'admin', $params));
         $href = href($WAS_SCRIPT_NAME, $this->a_params(TASK_USER_GROUPSAVE, $user_id));
         $this->output->add_content(dialog_quickform($href, $dialogdef));
         $this->show_menu_user($user_id, TASK_USER_GROUPS);
         $parameters = $this->a_params(TASK_USER_GROUPADD, $user_id);
         $anchor = t('usermanager_user_groups_add', 'admin');
         $attributes = array('title' => t('usermanager_user_groups_add_title', 'admin'));
         $this->output->add_breadcrumb($WAS_SCRIPT_NAME, $parameters, $attributes, $anchor);
         return;
     }
     //
     // 3 -- save the selected group/capacity to this user account
     //
     $key = $dialogdef['user_group_capacity']['value'];
     list($group_id, $capacity_code) = explode(':', $key);
     $group_id = intval($group_id);
     $capacity_code = intval($capacity_code);
     if ($group_id == 0 || $capacity_code == 0) {
         // the key '0:0' is used to indicate 'no more groups'; pretend that the user cancelled add group membership
         $this->output->add_message(t('cancelled', 'admin'));
         $this->user_groups();
         return;
     }
     $errors = 0;
     // assume all goes well, for now...
     $fields = array('user_id' => $user_id, 'group_id' => $group_id, 'capacity_code' => $capacity_code);
     $table = 'users_groups_capacities';
     if (db_insert_into($table, $fields) === FALSE) {
         // Mmmm, weird. Perhaps there was already a record for $user_id,$group_id with another $capacity_code?
         logger("usermanager: weird: add membership for user '{$user_id}' and group '{$group_id}' failed: " . db_errormessage());
         ++$errors;
         $where = array('user_id' => $user_id, 'group_id' => $group_id);
         if (db_delete($table, $where) === FALSE) {
             logger("usermanager: add membership double-fault, giving up: " . db_errormessage());
             ++$errors;
         } elseif (db_insert_into($table, $fields) === FALSE) {
             logger("usermanager: add membership failed again, giving up: " . db_errormessage());
             ++$errors;
         } else {
             logger("usermanager: add membership for user '{$user_id}' and group '{$group_id}' succeeded, finally");
             $errors = 0;
             // OK. Forget the initial error.
         }
     }
     if ($errors == 0) {
         $this->output->add_message(t('success_saving_data', 'admin'));
     } else {
         $this->output->add_message(t('errors_saving_data', 'admin', array('{ERRORS}' => $errors)));
     }
     $this->user_groups();
 }
 /** save the modified translations in a file in the tree CFG->datadir/languages/
  *
  * this routine validates the dialog data and attempts to save the changes in the file
  * $full_domain in the directory CFG->datadir/languages/$language_key/. Also, we may
  * need to send a message to the Website@School project with our changes (depending
  * on the flag _submit).
  *
  * @return void data saved and output written to browser via $this->output
  */
 function translation_save()
 {
     global $WAS_SCRIPT_NAME, $LANGUAGE;
     $language_key = get_parameter_string(TRANSLATETOOL_PARAM_LANGUAGE_KEY);
     // 1A -- basic sanity for language
     if ($this->languages === FALSE || !isset($this->languages[$language_key])) {
         logger(sprintf('%s.%s(): weird: user tried to edit translations for non-existing language \'%s\'', __CLASS__, __FUNCTION__, htmlspecialchars($language_key)));
         $params = array('{LANGUAGE_KEY}' => htmlspecialchars($language_key));
         $this->output->add_message(t('invalid_language', 'admin', $params));
         $this->languages_overview();
         return;
     }
     $full_domain = get_parameter_string(TRANSLATETOOL_PARAM_DOMAIN, 'was');
     // 1B -- basic sanity for domain
     if (!isset($this->domains[$full_domain])) {
         logger(sprintf('%s.%s(): weird: user requested non-existing domain \'%s\'', __CLASS__, __FUNCTION__, htmlspecialchars($full_domain)));
         $params = array('{FULL_DOMAIN}' => htmlspecialchars($full_domain));
         $this->output->add_message(t('invalid_language_domain', 'admin', $params));
         $this->languages_overview();
         return;
     }
     // 2 -- if the user cancelled the operation, there is no point in hanging 'round
     if (isset($_POST['button_cancel'])) {
         $this->output->add_message(t('cancelled', 'admin'));
         $this->languages_overview();
         return;
     }
     // 3 -- validate the dialog and maybe redo it
     $dialogdef = $this->get_dialogdef_language_domain($language_key, $full_domain);
     if (!dialog_validate($dialogdef)) {
         // there were errors, show them to the user and do it again
         foreach ($dialogdef as $k => $item) {
             if (isset($item['errors']) && $item['errors'] > 0) {
                 $this->output->add_message($item['error_messages']);
             }
         }
         $example_html = '<strong>';
         $example_variable = '{VALUE}';
         $example_tilde = '~';
         $examples = array('{EXAMPLE_HTML}' => $this->code_highlight($example_html), '{EXAMPLE_VARIABLE}' => $this->code_highlight($example_variable), '{EXAMPLE_TILDE}' => $this->code_highlight($example_tilde));
         $params = array('{LANGUAGE_KEY}' => $language_key, '{LANGUAGE_NAME}' => $this->languages[$language_key]['language_name'], '{FULL_DOMAIN}' => $this->domains[$full_domain]['title']);
         $this->output->add_content('<h2>' . t('translatetool_edit_language_domain_header', 'admin', $params) . '</h2>');
         $this->output->add_content(t('translatetool_edit_language_domain_explanation', 'admin', $examples));
         $this->output->add_content('<p>');
         $href = href($WAS_SCRIPT_NAME, $this->a_param(TRANSLATETOOL_CHORE_SAVE, $language_key, $full_domain));
         $this->output->add_content($this->render_translation_dialog($href, $dialogdef));
         // no menu this time, let user concentrate on task at hand ie errorfree data input
         return;
     }
     // 4 -- actually proceed with saving the data
     // 4A -- construct a diff between the system translation (if any) and the dialogdef values
     $strings = array();
     $dummy = array();
     $diff = array();
     $this->get_strings_system($language_key, $full_domain, $strings, $dummy);
     foreach ($dialogdef as $name => $item) {
         if (!isset($item['key']) || $item['type'] == F_SUBMIT) {
             // skip buttons and 'meta'-fields
             continue;
         }
         $key = $item['key'];
         # massage $value and standardise on \n as EOL
         $value = str_replace("\r\n", "\n", $item['value']);
         $value = str_replace("\n\r", "\n", $value);
         $value = str_replace("\r", "\n", $value);
         if (!isset($strings[$key]) || $strings[$key] != $value) {
             $diff[$key] = $value;
         }
     }
     // 4B -- if the diff is non-empty, go write and maybe submit it
     $params = array('{LANGUAGE_KEY}' => $language_key, '{LANGUAGE_NAME}' => $this->languages[$language_key]['language_name'], '{FULL_DOMAIN}' => $this->domains[$full_domain]['title']);
     $diff_count = sizeof($diff);
     if ($diff_count == 0) {
         // No changes need to be saved
         $this->output->add_message(t('translatetool_no_changes_to_save', 'admin', $params));
     } else {
         $diff['_submit'] = $dialogdef['_submit']['value'] == '1' ? '1' : '0';
         $diff['_full_name'] = $dialogdef['_full_name']['value'];
         $diff['_email'] = $dialogdef['_email']['value'];
         $diff['_notes'] = $dialogdef['_notes']['value'];
         $retval = $this->put_strings_userfile($language_key, $full_domain, $diff);
         if (!$retval) {
             logger(sprintf('%s.%s(): could not write translation file for %s - %s (%d items)', __CLASS__, __FUNCTION__, $language_key, $full_domain, $diff_count));
         } else {
             logger(sprintf('%s.%s(): success writing translation file for %s - %s: %d items', __CLASS__, __FUNCTION__, $language_key, $full_domain, $diff_count), WLOG_DEBUG);
             if (db_bool_is(FALSE, $this->languages[$language_key]['dialect_in_file'])) {
                 $table = 'languages';
                 $fields = array('dialect_in_file' => TRUE);
                 $where = array('language_key' => $language_key);
                 $retval = db_update($table, $fields, $where);
                 if ($retval) {
                     logger(sprintf('%s.%s(): updated language %s: dialect_in_file is now enabled', __CLASS__, __FUNCTION__, $language_key), WLOG_DEBUG);
                 } else {
                     logger(sprintf('%s.%s(): update of language %s failed: %s', __CLASS__, __FUNCTION__, $language_key, db_errormessage()));
                 }
             }
         }
         if ($retval) {
             $this->output->add_message(t('translatetool_translation_save_success', 'admin', $params));
         } else {
             $this->output->add_message(t('translatetool_translation_save_failure', 'admin', $params));
         }
         if ($diff['_submit'] == '1') {
             if ($this->submit_diff_to_project($language_key, $full_domain, $diff)) {
                 $this->output->add_message(t('translatetool_translation_submit_success', 'admin', $params));
                 logger(sprintf('%s.%s(): success submitting translations for %s - %s: %d items', __CLASS__, __FUNCTION__, $language_key, $full_domain, $diff_count), WLOG_DEBUG);
             } else {
                 logger(sprintf('%s.%s(): could not submit translations for %s - %s (%d items)', __CLASS__, __FUNCTION__, $language_key, $full_domain, $diff_count));
                 $this->output->add_message(t('translatetool_translation_submit_failure', 'admin', $params));
             }
         }
     }
     // 5 -- clear cache and force reread of translation just saved
     $LANGUAGE->reset_cache($language_key, $full_domain);
     $this->translation_edit();
 }