/** 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(); }