/** present the user with a dialog to modify the snapshots that are connected to node $node_id
 *
 * this prepares a dialog for the user filled with existing data (if any), possibly allowing
 * the user to modify the content. If the flag $viewonly is TRUE, this routine should only
 * display the content rather than let the user edit it. If the flag $edit_again is TRUE,
 * the routine should use the data available in the $_POST array, otherwise it should read
 * the data from the database (or wherever the data comes from). The parameter $href is the
 * place where the form should be POST'ed.
 *
 * The dialog should be added to the $output object. Useful routines are:
 * <code>
 * $output->add_content($content): add $content to the content area
 * $output->add_message($message): add $message to the message area (feedback to the user)
 * $output->add_popup_bottom($message): make $message popup in the browser after loading the page (uses javascript)
 * $output->add_popup_top($message): make $message popup in the browser before loading the page (uses javascript)
 * </code>
 * 
 * @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 this module is connected
 * @param array $module the module record straight from the database
 * @param bool $viewonly if TRUE, editing is not allowed (but simply showing the content is allowed)
 * @param bool $edit_again if TRUE start with data from $_POST, else use data from database
 * @param string $href the action property of the HTML-form, the place where data will be POST'ed
 * @return bool TRUE on success + output stored via $output, FALSE otherwise
 * @todo we might want to jazz up this dialog by adding some sort of 'directory browser'
 *       for the snapshots_path field using a pop-up window. Mmmmm.... future refinements...
 */
function snapshots_show_edit(&$output, $area_id, $node_id, $module, $viewonly, $edit_again, $href)
{
    $dialogdef = snapshots_get_dialogdef($viewonly);
    if ($edit_again) {
        // retrieve and validate the POSTed values
        dialog_validate($dialogdef);
        // no need to show messages; we did that alread in snapshots_save() below
        snapshots_check_path($dialogdef['snapshots_path'], $area_id, $node_id);
    } else {
        // make a fresh start with data from the database
        $where = array('node_id' => intval($node_id));
        if (($record = db_select_single_record('snapshots', '*', $where)) === FALSE) {
            logger(sprintf('%s(): error retrieving snapshots configuration: %s', __FUNCTION__, db_errormessage()));
        } else {
            foreach ($dialogdef as $name => $item) {
                switch ($name) {
                    case 'header':
                    case 'introduction':
                    case 'snapshots_path':
                    case 'variant':
                    case 'dimension':
                        $dialogdef[$name]['value'] = strval($record[$name]);
                        break;
                }
            }
        }
    }
    $output->add_content('<h2>' . t('snapshots_content_header', 'm_snapshots') . '</h2>');
    $output->add_content(t('snapshots_content_explanation', 'm_snapshots'));
    $output->add_content(dialog_quickform($href, $dialogdef));
    return TRUE;
}
 /** show the name of an area and ask the user for a confirmation of deletion
  *
  * this displays a confirmation question for deletion of an area.
  * If the user presses Delete button, the area will be deleted,
  * if the user presses Cancel then nothing is deleted.
  *
  * @param int $area_id
  * @param array &$areas records with area information of all areas
  * @return void results are returned as output in $this->output
  */
 function show_dialog_confirm_delete($area_id, &$areas)
 {
     global $WAS_SCRIPT_NAME;
     $dialogdef = array('dialog' => array('type' => F_INTEGER, 'name' => 'dialog', 'value' => AREAMANAGER_DIALOG_DELETE, 'hidden' => TRUE), dialog_buttondef(BUTTON_DELETE), dialog_buttondef(BUTTON_CANCEL));
     $area = $areas[$area_id];
     $params = array('{AREA}' => $area_id, '{AREA_FULL_NAME}' => $area['title']);
     $this->output->add_content('<h2>' . t('delete_an_area_header', 'admin', $params) . '</h2>');
     $this->output->add_content(t('delete_area_explanation', 'admin'));
     $this->output->add_content('<ul>');
     $area_description = t(db_bool_is(TRUE, $area['is_private']) ? 'area_delete_private_title' : 'area_delete_public_title', 'admin', $params);
     $area_description .= db_bool_is(TRUE, $area['is_active']) ? '' : ' (' . t('inactive', 'admin') . ')';
     $this->output->add_content('  <li class="level0">' . $area_description);
     $this->output->add_content('</ul>');
     $this->output->add_content(t('delete_are_you_sure', 'admin'));
     $href = href($WAS_SCRIPT_NAME, $this->a_param(AREAMANAGER_CHORE_DELETE, $area_id));
     $this->output->add_content(dialog_quickform($href, $dialogdef));
 }
/** show a preview of the message to the visitor
 *
 * this shows a preview of the message to visitor.
 * Nothing is editable, it is view-only. The only option
 * is to either press the Send-button to actually send
 * the messate OR to press the Edit button to go back
 * to the editable form.
 *
 * Sending a message is a two-step procedure by design.
 *
 * @param object &$theme collects the (html) output
 * @param array mailpage configuration data in a (nested) array
 * @param array $dialogdef array that defines the input fields
 * @param string $ip_addr the originating IP-address
 * @return void output writted to $theme
 */
function mailpage_show_preview(&$theme, $config, $dialogdef, $ip_addr)
{
    $destinations = $forbidden = array(chr(10), chr(13), chr(34), '\\');
    $mailfrom = sprintf('&quot;%s&quot; &lt;%s&gt;', htmlspecialchars(str_replace($forbidden, '', trim($dialogdef['fullname']['value']))), htmlspecialchars(str_replace($forbidden, '', trim($dialogdef['email']['value']))));
    $subject = htmlspecialchars(trim($dialogdef['subject']['value']));
    $message = nl2br(htmlspecialchars(trim($dialogdef['message']['value'])));
    $remote_addr = htmlspecialchars($ip_addr);
    //
    // 2 -- actually output the preview
    //
    $theme->add_content(html_tag('h2', '', t('preview_header', 'm_mailpage')));
    $theme->add_content('<div>');
    $theme->add_content(sprintf('<strong>%s</strong>: %s<br>', t('from', 'm_mailpage'), $mailfrom));
    if (1 < sizeof($config['addresses'])) {
        // only show destination if there is more than 1
        $index = isset($dialogdef['destination']) ? $dialogdef['destination']['value'] : 0;
        $destination = $config['addresses'][$index]['name'];
        $sendto = htmlspecialchars('"' . str_replace($forbidden, '', trim($destination)) . '"');
        $theme->add_content(sprintf('<strong>%s</strong>: %s<br>', t('to', 'm_mailpage'), $sendto));
    }
    $theme->add_content(sprintf('<strong>%s</strong>: %s<br>', t('subject', 'm_mailpage'), $subject));
    $theme->add_content(sprintf('<strong>%s</strong>: %s<br>', t('date', 'm_mailpage'), date('r')));
    $theme->add_content(sprintf('<strong>%s</strong>: %s<br>', t('ip_addr', 'm_mailpage'), $remote_addr));
    $theme->add_content(sprintf("<strong>%s</strong>:<br>\n%s<br>", t('message', 'm_mailpage'), $message));
    $theme->add_content('</div>');
    //
    // 3 -- finish with navigation for the visitor
    //
    $previewdef = array('token' => $dialogdef['token'], 'button_edit' => dialog_buttondef(BUTTON_EDIT), 'button_send' => dialog_buttondef('button_send', t('button_send', 'm_mailpage')), 'button_cancel' => dialog_buttondef(BUTTON_CANCEL));
    $href = was_node_url($theme->node_record);
    $theme->add_content(dialog_quickform($href, $previewdef));
}
 /** show a dialog that ask the user to confirm the removal of a directory
  *
  * Show the first directory from $entries_to_delete and ask the user to confirm
  * with [Delete] button or to cancel with [Cancel] button. The name of the directory
  * to delete is part of the href rather than a POSTed field. We only allow a single
  * directory to be removed at a time.
  *
  * @param string $path the working directory
  * @param array $entries_to_delete an array with directory entries identifying the files to delete keyed by name
  * @return dialog is displayed via $this->output
  */
 function show_dialog_confirm_delete_directory($path, $entries_to_delete)
 {
     global $WAS_SCRIPT_NAME;
     $dialogdef = array('confirm' => array('type' => F_INTEGER, 'name' => 'confirm', 'value' => '1', 'hidden' => TRUE), 'button_delete' => dialog_buttondef(BUTTON_DELETE), 'button_cancel' => dialog_buttondef(BUTTON_CANCEL));
     $params = array('{PATH}' => htmlspecialchars($this->vpath($path)));
     $this->output->add_content('<h2>' . t('filemanager_delete_directory_header', 'admin') . '</h2>');
     $this->output->add_content(t('filemanager_delete_directory_explanation', 'admin', $params));
     $this->output->add_content('<ul>');
     $entry = reset($entries_to_delete);
     $this->output->add_content('  <li class="level0">' . htmlspecialchars($entry['vname']));
     $this->output->add_content('</ul>');
     $this->output->add_content(t('delete_are_you_sure', 'admin'));
     $href = href($WAS_SCRIPT_NAME, array('job' => $this->job, 'task' => TASK_REMOVE_DIRECTORY, PARAM_PATH => $entry['path']));
     $this->output->add_content(dialog_quickform($href, $dialogdef));
     $this->show_breadcrumbs($path);
 }
/** display the current version of the document and maybe an Edit button
 *
 * this fetches a fresh version of the document from the database
 * and displays it, with the header and the introduction (if any).
 *
 * If the writer flag is TRUE, we also display the date/time/user 
 * of last update to the document plus we generate an Edit button
 * which allows this user to actually edit the document via CREW.
 *
 * Note
 * Since CREW requires JavaScript, we use a trick: we initially
 * hide the Edit button (and the Skin listbox) by setting the
 * display-parameter of the surrounding DIV to none. Subsequently
 * we set that parameter to 'block' using JavaScript. That means
 * if JaveScript is not enabled, the user will not see the DIV
 * and hence the button. If JS is enabled there is no problem.
 * As an added bonus we also have a single line of text warning
 * the user about JavaScript via a NOSCTIPT-tag. The only thing
 * that can happen now is that the browser does not support the
 * Websocket protocl. This is dealt with in the actual CREW code.
 *
 * @param object &$theme collects the (html) output
 * @param bool $writer if TRUE we add an Edit button to the display
 * @param int $node_id key to the page we're generating
 * @return bool TRUE on success+output generated via $theme, FALSE otherwise
 */
function crew_view_show_view(&$theme, $writer, $node_id)
{
    global $CFG;
    $theme->add_stylesheet($CFG->progwww_short . '/modules/crew/crew_view.css');
    // 1 -- get the necessary information we need from database
    if (($record = crew_view_get_workshop_data($node_id)) === FALSE) {
        $theme->add_message(t('error_retrieving_workshop_data', 'm_crew'));
        return FALSE;
    }
    // 2 -- massage and display the data
    $header = trim($record['header']);
    $introduction = trim($record['introduction']);
    if (!empty($header)) {
        $attr = array('class' => 'crew_header');
        $theme->add_content(html_tag('h2', $attr, $header));
    }
    if (!empty($introduction)) {
        $attr = array('class' => 'crew_introduction');
        $theme->add_content(html_tag('div', $attr, $introduction));
    }
    $attr = array('class' => 'crew_document');
    $document = nl2br(htmlspecialchars($record['document']));
    $theme->add_content(html_tag('div', $attr, $document));
    // 3A -- maybe display the document time stamp (only if user qualifies as a writer)
    if ($writer) {
        $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('div', $attr, t('last_updated_by', 'm_crew', $params)));
    }
    // 3B -- maybe display an Edit button (only if user qualifies)
    if ($writer) {
        // only writer get to see the Edit button (if Javascript AND Websockets available)
        $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));
        $dialogdef = crew_view_dialogdef();
        $href = was_node_url($theme->node_record);
        $theme->add_content(dialog_quickform($href, $dialogdef));
        $theme->add_content(html_tag_close('div'));
        $js = "document.getElementById('crew_start_edit').style.display='block';";
        $theme->add_content(html_tag('script', '', $js));
    }
}
 /** 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);
 }
 /** add a complete dialog to the content area of the output
  *
  * @param object &$output the object that collects the output
  * @param string $href the target for the form that will be created
  * @result output added to content part of output object
  * @uses dialog_quickform()
  */
 function show_dialog(&$output, $href)
 {
     if (empty($this->dialogdef)) {
         $this->dialogdef = $this->get_dialogdef();
     }
     $output->add_content(dialog_quickform($href, $this->dialogdef));
 }
/** present the user with a dialog to modify the content that is connected to node $node_id
 *
 * this prepares a dialog for the user filled with existing data (if any), possibly allowing
 * the user to modify the content. If the flag $viewonly is TRUE, this routine should only
 * display the content rather than let the user edit it. If the flag $edit_again is TRUE,
 * the routine should use the data available in the $_POST array, otherwise it should read
 * the data from the database (or wherever the data comes from). The parameter $href is the
 * place where the form should be POST'ed.
 *
 * The dialog should be added to the $output object. Useful routines are:
 * <code>
 * $output->add_content($content): add $content to the content area
 * $output->add_message($message): add $message to the message area (feedback to the user)
 * $output->add_popup_bottom($message): make $message popup in the browser after loading the page (uses javascript)
 * $output->add_popup_top($message): make $message popup in the browser before loading the page (uses javascript)
 * </code>
 * 
 * @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 this module is connected
 * @param array $module the module record straight from the database
 * @param bool $viewonly if TRUE, editing is not allowed (but simply showing the content is allowed)
 * @param bool $edit_again if TRUE start with data from $_POST, else use data from database
 * @param string $href the action property of the HTML-form, the placa where data will be POST'ed
 * @return bool TRUE on success + output stored via $output, FALSE otherwise
 */
function guestbook_show_edit(&$output, $area_id, $node_id, $module, $viewonly, $edit_again, $href)
{
    $output->add_content('<h2>STUB - Edit Content Dialog (' . $module['name'] . ')</h2>');
    $output->add_content('STUB: ' . __FUNCTION__ . '() in ' . __FILE__ . ' (' . __LINE__ . ')' . "<br>viewonly = {$viewonly}<br>edit_again = {$edit_again}<br>href = {$href}<p>\n" . "Note that this is just a stub with two buttons. It really does nothing but exercise the module interface.<p>\n");
    $dialogdef = array(array('type' => F_CHECKBOX, 'name' => 'fail', 'options' => array(1 => 'STUB: Check the bo~x to let the save routine fail'), 'title' => 'STUB - check the box to test failure of the save routine()'), dialog_buttondef(BUTTON_SAVE), dialog_buttondef(BUTTON_CANCEL));
    $output->add_content(dialog_quickform($href, $dialogdef));
    return TRUE;
}
/** present the user with a dialog to modify the redirect that is connected to node $node_id
 *
 * this prepares a dialog for the user filled with existing data (if any), possibly allowing
 * the user to modify the content. If the flag $viewonly is TRUE, this routine should only
 * display the content rather than let the user edit it. If the flag $edit_again is TRUE,
 * the routine should use the data available in the $_POST array, otherwise it should read
 * the data from the database (or wherever the data comes from). The parameter $href is the
 * place where the form should be POST'ed.
 *
 * The dialog should be added to the $output object. Useful routines are:
 * <code>
 * $output->add_content($content): add $content to the content area
 * $output->add_message($message): add $message to the message area (feedback to the user)
 * $output->add_popup_bottom($message): make $message popup in the browser after loading the page (uses javascript)
 * $output->add_popup_top($message): make $message popup in the browser before loading the page (uses javascript)
 * </code>
 * 
 * @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 this module is connected
 * @param array $module the module record straight from the database
 * @param bool $viewonly if TRUE, editing is not allowed (but simply showing the content is allowed)
 * @param bool $edit_again if TRUE start with data from $_POST, else use data from database
 * @param string $href the action property of the HTML-form, the place where data will be POST'ed
 * @return bool TRUE on success + output stored via $output, FALSE otherwise
 */
function redirect_show_edit(&$output, $area_id, $node_id, $module, $viewonly, $edit_again, $href)
{
    $dialogdef = redirect_get_dialogdef($viewonly);
    if ($edit_again) {
        // retrieve and validate the POSTed values
        dialog_validate($dialogdef);
        // no need to show messages; we did that alread in redirect_save() bel0w
    } else {
        // make a fresh start with data from the database
        $table = 'nodes';
        $fields = array('link_href', 'link_target');
        $where = array('node_id' => intval($node_id));
        if (($record = db_select_single_record($table, $fields, $where)) === FALSE) {
            logger(sprintf('%s(): error retrieving node record for redirect configuration: %s', __FUNCTION__, db_errormessage()));
        } else {
            foreach ($dialogdef as $name => $item) {
                switch ($name) {
                    case 'link_href':
                    case 'link_target':
                        $dialogdef[$name]['value'] = strval($record[$name]);
                        break;
                }
            }
        }
    }
    $output->add_content('<h2>' . t('redirect_content_header', 'm_redirect') . '</h2>');
    $output->add_content(t('redirect_content_explanation', 'm_redirect'));
    $output->add_content(dialog_quickform($href, $dialogdef));
    return TRUE;
}
/** present the user with a dialog to modify the mailpage that is connected to node $node_id
 *
 * this prepares a dialog for the user filled with existing data (if any), possibly allowing
 * the user to modify the content. If the flag $viewonly is TRUE, this routine should only
 * display the content rather than let the user edit it. If the flag $edit_again is TRUE,
 * the routine should use the data available in the $_POST array, otherwise it should read
 * the data from the database (or wherever the data comes from). The parameter $href is the
 * place where the form should be POST'ed (but see the notes below).
 *
 * Note that the mailpage module uses two different configuration tables.
 *
 * The first is 'mailpages' which holds the configuration for a single mailpage in a single record.
 * The second one is 'mailpages_addresses' which holds 0, 1 or more addresses per mailpage, and
 * thus has more records per mailpage. The way to deal with this 1-on-N situation (imho) is
 * to actually have two dialogs that the user can interact with.
 *
 * The first dialog looks something like this:
 * <pre>
 * _Add an address_
 * _Address 1_
 * _Address 2_
 * ...
 * Header:          __________
 * Introduction:    __________
 * Default message: __________
 * [Save] [Cancel]
 * </pre>
 *
 * where the clickable links _Add an address_,...,_Address 1_ link to the second dialog:
 * <pre>
 * Name:         __________
 * E-mail:       __________
 * Description:  __________
 * Thank-you:    __________
 * Sort order:   __________
 * [Save] [Cancel] [Delete]
 * </pre>
 *
 * This dialog allows for manipulating addresses to go with the mailpage.
 * The clickable link that leads to dialog #2 can be distinguished from the
 * main dialog via the GET-parameter 'address' which holds the unique pkey
 * 'mailpage_address_id' of the address, or 0 in case of a new address to add.
 *
 * Since the module interface is mostly designed to work with a single dialog
 * (via <modulename>_show_edit() and <modulename>_save()) there is a challenge
 * to get two dialogs working from those two routines.
 *
 * The best I could think of is to add the parameter 'address' to the URLs:
 * if it is there we need to deal with a dialog #2 and if it isn't we deal with
 * dialog #1. We need to take care that the buttons in dialog #2 only take the
 * user back to dialog #1 rather than end the editing of the dialog alltogether.
 * The buttons in dialog #1 should end the editing, however. Mmm, tricky.
 *
 * The trick is to 'abuse' the return value of 'edit_again': if that parameter
 * is TRUE, the <module>_save() routine eventually yields a call to <module>_show_edit.
 * If we test for 'edit_again' AND GET['address'] we can isolate the case where
 * dialog #2 returns 'edit_again' even though the data in dialog #2 was valid
 * (as determined by checking the dialog again). So, it is a little complicated
 * (understatement) but it could work, at least from the POV of the user.
 *
 * There is one other thing: we only get the $href to POST to. It _should_
 * be used 'as-is', but we know (from Page Manager) that it is in fact a link
 * to admin.php with a 'job', 'task' and 'node' parameter. We can simply add
 * an extra parameter by appending '&address=nnn' to $href, but that does not
 * give us access to the task TASK_NODE_EDIT_CONTENT but only to the task
 * TASK_SAVE_CONTENT. Aaarrgghhhh, it seems we need to violate the interface
 * and construct our own $href via
 * <code>
 * $href=href($WAS_SCRIPT_NAME,array('job'=>JOB_PAGEMANAGER,'task'=>TASK_XXX_YYY,'node'=>$node_id));
 * </code>
 * rather than using the $href provided. Ugly. I'm sorry about that.
 *
 * The dialog should be added to the $output object. Useful routines are:
 * <code>
 * $output->add_content($content): add $content to the content area
 * $output->add_message($message): add $message to the message area (feedback to the user)
 * $output->add_popup_bottom($message): make $message popup in the browser after loading the page (uses JS)
 * $output->add_popup_top($message): make $message popup in the browser before loading the page (uses JS)
 * </code>
 * 
 * @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 this module is connected
 * @param array $module the module record straight from the database
 * @param bool $viewonly if TRUE, editing is not allowed (but simply showing the content is allowed)
 * @param bool $edit_again if TRUE start with data from $_POST, else use data from database
 * @param string $href the action property of the HTML-form, the place where data will be POST'ed
 * @return bool TRUE on success + output stored via $output, FALSE otherwise
 */
function mailpage_show_edit(&$output, $area_id, $node_id, $module, $viewonly, $edit_again, $href)
{
    global $WAS_SCRIPT_NAME;
    $records = mailpage_get_addresses($node_id);
    $sort_order = 10 * (1 + sizeof($records));
    // $records are always renumbered so this is the first largest sord_order
    //
    // 1 -- determine which dialog to show based on GET['address'], $edit_again and validate($dialog2)
    //
    $address_id = get_parameter_int('address', NULL);
    if (is_null($address_id) || $address_id != 0 && !isset($records[$address_id])) {
        $dialogdef = mailpage_get_dialogdef_config($output, $viewonly, $node_id);
        if ($edit_again && !isset($_POST['button_delete'])) {
            // retrieve and validate POSTed values
            mailpage_dialog_validate($dialogdef, $node_id, $records);
            // don't show messages; alrady did that
        }
        $dialog = 1;
    } else {
        // $address_id is either 0 (new address to add) OR is a valid existing address_id
        $dialogdef = mailpage_get_dialogdef_address($output, $viewonly, $node_id, $address_id, $sort_order);
        if ($edit_again) {
            if (dialog_validate($dialogdef)) {
                $dialogdef = mailpage_get_dialogdef_config($output, $viewonly, $node_id);
                $dialog = 1;
            } else {
                $dialog = 2;
            }
        } else {
            $dialog = 2;
        }
    }
    //
    // 2 -- actually show the selected dialog
    //
    if ($dialog == 1) {
        $output->add_content('<h2>' . t('mailpage_content_header', 'm_mailpage') . '</h2>');
        $output->add_content(t('mailpage_content_explanation', 'm_mailpage'));
        $params = array('job' => JOB_PAGEMANAGER, 'task' => TASK_NODE_EDIT_CONTENT, 'node' => $node_id, 'address' => '0');
        $output->add_content('<p>');
        if (!$viewonly) {
            // cannot add records in view-only mode
            $attributes = array('title' => t('add_new_address_title', 'm_mailpage'));
            $anchor = t('add_new_address_label', 'm_mailpage');
            $output->add_content(html_a($WAS_SCRIPT_NAME, $params, $attributes, $anchor) . '<br>');
        }
        foreach ($records as $record) {
            $params['address'] = $record['mailpage_address_id'];
            $aparams = array('{NAME}' => $record['name'], '{EMAIL}' => $record['email'], '{SORT_ORDER}' => $record['sort_order'], '{ADDRESS_ID}' => $record['mailpage_address_id']);
            $anchor = t('edit_address_label', 'm_mailpage', $aparams);
            $attributes = array('title' => t('edit_address_title', 'm_mailpage', $aparams));
            $output->add_content(html_a($WAS_SCRIPT_NAME, $params, $attributes, $anchor) . '<br>');
        }
        $output->add_content('</p>');
        $output->add_content(dialog_quickform($href, $dialogdef));
    } else {
        if ($address_id != 0) {
            $output->add_content('<h2>' . t('mailpage_edit_address_header', 'm_mailpage') . '</h2>');
            $output->add_content(t('mailpage_edit_address_explanation', 'm_mailpage'));
        } else {
            $output->add_content('<h2>' . t('mailpage_add_address_header', 'm_mailpage') . '</h2>');
            $output->add_content(t('mailpage_add_address_explanation', 'm_mailpage'));
        }
        // Alas, we need to construct our own href because of adding $address_id
        $href = href($WAS_SCRIPT_NAME, array('job' => JOB_PAGEMANAGER, 'task' => TASK_SAVE_CONTENT, 'node' => $node_id, 'address' => strval($address_id)));
        $output->add_content(dialog_quickform($href, $dialogdef));
    }
    return TRUE;
}
 /** delete a group after confirmation
  *
  * this either presents a confirmation dialog to the user OR deletes a group with
  * associated capacities and acls.
  *
  * Note that this routine could have been split into two routines, with the
  * first one displaying the confirmation dialog and the second one 'saving the changes'.
  * However, I think it is counter-intuitive to perform a deletion of data under
  * the name of 'saving'. So, I decided to use the same routine for both displaying
  * the dialog and acting on the dialog.
  *
  * @return void results are returned as output in $this->output
  * @todo since multiple tables are involved, shouldn't we use transaction/rollback/commit?
  *       Q: How well is MySQL suited for transactions? A: Mmmmm.... Which version? Which storage engine?
  */
 function group_delete()
 {
     global $WAS_SCRIPT_NAME, $DB, $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;
     }
     // 2A -- do not allow the user to remove a group associated with the user
     $where = array('user_id' => intval($USER->user_id), 'group_id' => $group_id);
     if (($record = db_select_single_record('users_groups_capacities', 'capacity_code', $where)) !== FALSE) {
         // Oops, the current user happens to be a member of this group
         $params = $this->get_group_capacity_names($group_id, $record['capacity_code']);
         logger(sprintf("%s.%s(): user attempts to remove group '%s' ('%d', '%s') but she is a '%s'", __CLASS__, __FUNCTION__, $params['{GROUP}'], $group_id, $params['{GROUP_FULL_NAME}'], $params['{CAPACITY}']));
         $this->output->add_message(t('usermanager_delete_group_not_self', 'admin', $params));
         $this->groups_overview();
         return;
     }
     // 2B -- are there any files left in this user's private storage $CFG->datadir.'/groups/'.$path?
     if (($group = $this->get_group_record($group_id)) === FALSE) {
         $this->groups_overview();
         return;
     }
     $path = '/groups/' . $group['path'];
     if (!userdir_is_empty($path)) {
         // At this point we know there are still files associated with this
         // group in the data directory. This is a show stopper; it is up to the
         // admin requesting this delete to get rid of the files first (eg via File Manager)
         logger(sprintf("%s.%s(): data directory '%s' not empty", __CLASS__, __FUNCTION__, $path));
         $params = $this->get_group_capacity_names($group_id);
         $this->output->add_message(t('usermanager_delete_group_dir_not_empty', 'admin', $params));
         $this->groups_overview();
         return;
     }
     //
     // 3 -- user has confirmed delete?
     //
     if (isset($_POST['button_delete']) && isset($_POST['dialog']) && $_POST['dialog'] == GROUPMANAGER_DIALOG_DELETE) {
         $params = $this->get_group_capacity_names($group_id);
         // pick up name before it is gone
         if (userdir_delete($path) && $this->delete_group_capacities_records($group_id)) {
             $this->output->add_message(t('groupmanager_delete_group_success', 'admin', $params));
             $retval = TRUE;
         } else {
             $this->output->add_message(t('groupmanager_delete_group_failure', 'admin', $params));
             $retval = FALSE;
         }
         logger(sprintf("%s.%s(): %s deleting group '%d' %s (%s)", __CLASS__, __FUNCTION__, $retval === FALSE ? 'failure' : 'success', $group_id, $params['{GROUP}'], $params['{GROUP_FULL_NAME}']));
         $this->groups_overview();
         return;
     }
     //
     // 4 -- no delete yet, first show confirmation dialog
     //
     // Dialog is very simple: a simple text showing
     // - the name of the group
     // - the names of the associated capacities with number of users associated
     // - a Delete and a Cancel button
     //
     $dialogdef = array('dialog' => array('type' => F_INTEGER, 'name' => 'dialog', 'value' => GROUPMANAGER_DIALOG_DELETE, 'hidden' => TRUE), 'button_save' => dialog_buttondef(BUTTON_DELETE), 'button_cancel' => dialog_buttondef(BUTTON_CANCEL));
     $params = $this->get_group_capacity_names($group_id);
     $header = t('groupmanager_delete_group_header', 'admin', $params);
     $this->output->add_content('<h2>' . $header . '</h2>');
     $this->output->add_content(t('groupmanager_delete_group_explanation', 'admin'));
     $this->output->add_content('<ul>');
     $this->output->add_content('  <li class="level0">' . t('groupmanager_delete_group_group', 'admin', $params));
     $sql = sprintf("SELECT gc.capacity_code, COUNT(ugc.user_id) AS users " . "FROM %sgroups_capacities gc LEFT JOIN %susers_groups_capacities ugc " . "ON gc.group_id = ugc.group_id AND gc.capacity_code = ugc.capacity_code " . "WHERE gc.group_id = %d " . "GROUP BY gc.capacity_code " . "ORDER BY gc.sort_order", $DB->prefix, $DB->prefix, $group_id);
     if (($DBResult = $DB->query($sql)) !== FALSE) {
         $records = $DBResult->fetch_all_assoc();
         $DBResult->close();
         foreach ($records as $record) {
             $params['{CAPACITY}'] = capacity_name($record['capacity_code']);
             $params['{COUNT}'] = $record['users'];
             $line = t('groupmanager_delete_group_capacity', 'admin', $params);
             $this->output->add_content('  <li class="level0">' . $line);
         }
     }
     $this->output->add_content('</ul>');
     $this->output->add_content(t('delete_are_you_sure', 'admin'));
     $a_params = $this->a_params(TASK_GROUP_DELETE, $group_id);
     $href = href($WAS_SCRIPT_NAME, $a_params);
     $this->output->add_content(dialog_quickform($href, $dialogdef));
     $this->show_menu_group($group_id, TASK_GROUP_DELETE);
     $this->output->add_breadcrumb($WAS_SCRIPT_NAME, $a_params, array('title' => $header), t('groupmanager_delete_group_breadcrumb', 'admin'));
 }
/** present the user with a dialog to modify the content that is connected to node $node_id
 *
 * this prepares a dialog for the user filled with existing data (if any), possibly allowing
 * the user to modify the content. If the flag $viewonly is TRUE, this routine should only
 * display the content rather than let the user edit it. If the flag $edit_again is TRUE,
 * the routine should use the data available in the $_POST array, otherwise it should read
 * the data from the database (or wherever the data comes from). The parameter $href is the
 * place where the form should be POST'ed.
 *
 * The dialog should be added to the $output object. Useful routines are:
 * <code>
 * $output->add_content($content): add $content to the content area
 * $output->add_message($message): add $message to the message area (feedback to the user)
 * $output->add_popup_bottom($message): make $message popup in the browser after loading the page (uses javascript)
 * $output->add_popup_top($message): make $message popup in the browser before loading the page (uses javascript)
 * </code>
 * 
 * @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 this module is connected
 * @param array $module the module record straight from the database
 * @param bool $viewonly if TRUE, editing is not allowed (but simply showing the content is allowed)
 * @param bool $edit_again if TRUE start with data from $_POST, else use data from database
 * @param string $href the action property of the HTML-form, the placa where data will be POST'ed
 * @return bool TRUE on success + output stored via $output, FALSE otherwise
 */
function htmlpage_show_edit(&$output, $area_id, $node_id, $module, $viewonly, $edit_again, $href)
{
    if ($edit_again) {
        $value = magic_unquote($_POST['htmlpage_content']);
    } else {
        $where = array('node_id' => intval($node_id));
        $order = array('version DESC');
        $retval = db_select_single_record('htmlpages', '*', $where, $order);
        if ($retval === FALSE) {
            $value = '??error??';
            $version = 0;
        } else {
            $value = $retval['page_data'];
            $version = $retval['version'];
        }
    }
    $dialogdef = array('htmlpage_content' => array('type' => F_RICHTEXT, 'name' => 'htmlpage_content', 'value' => $value, 'rows' => 20, 'columns' => 80, 'maxlength' => 655360, 'viewonly' => $viewonly, 'alt' => 'STUB: alttext goes here', 'title' => 'STUB: mouse-over goes here'));
    if (!$viewonly) {
        $dialogdef['button_save'] = dialog_buttondef(BUTTON_SAVE);
    }
    $dialogdef['button_cancel'] = dialog_buttondef(BUTTON_CANCEL);
    $output->add_content(dialog_quickform($href, $dialogdef));
    return TRUE;
}
 /** 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();
 }
 /** validate and save modified data to database
  *
  * this saves data from the edit language  dialog if data validates.
  * If the data does NOT validate, the edit screen is displayed again
  * otherwise the languages overview is displayed again.
  *
  * @return void results are returned as output in $this->output
  * @uses $WAS_SCRIPT_NAME
  * @uses $CFG
  * @uses $USER
  * @uses $LANGUAGE
  */
 function language_save()
 {
     global $CFG, $WAS_SCRIPT_NAME, $USER, $LANGUAGE;
     $language_key = get_parameter_string(TRANSLATETOOL_PARAM_LANGUAGE_KEY);
     // 1 -- basic sanity
     if ($this->languages === FALSE || !isset($this->languages[$language_key])) {
         // are they trying to trick us, specifying an invalid language?
         logger(sprintf('%s.%s(): weird: user tried to save properties of 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;
     }
     // 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 data; check for generic errors (string too short, number too small, etc)
     $dialogdef = $this->get_dialogdef_language($language_key);
     if (!dialog_validate($dialogdef)) {
         // show errors 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('translatetool_edit_language_header', 'admin') . '</h2>');
         $this->output->add_content(t('translatetool_edit_language_explanation', 'admin'));
         $href = href($WAS_SCRIPT_NAME, $this->a_param(TRANSLATETOOL_CHORE_LANGUAGE_SAVE, $language_key));
         $this->output->add_content(dialog_quickform($href, $dialogdef));
         return;
     }
     // 4 -- still here? go save changes
     $language_name = $dialogdef['language_name']['value'];
     $parent_key = $dialogdef['parent_language_key']['value'] == '--' ? NULL : $dialogdef['parent_language_key']['value'];
     $is_active = $dialogdef['language_is_active']['value'] == '1' ? TRUE : FALSE;
     $fields = array('parent_language_key' => $parent_key, 'language_name' => $language_name, 'is_active' => $is_active);
     $where = array('language_key' => $language_key);
     if (db_update('languages', $fields, $where) === FALSE) {
         logger(sprintf('%s.%s(): saving changes forlanguage \'%s\' failed: %s', __CLASS__, __FUNCTION__, htmlspecialchars($language_key), db_errormessage()));
         $this->output->add_message(t('translatetool_language_save_failure', 'admin'));
     } else {
         $params = array('{LANGUAGE_KEY}' => $language_key, '{LANGUAGE_NAME}' => $language_name);
         $this->output->add_message(t('translatetool_language_save_success', 'admin', $params));
         logger(sprintf("%s.%s(): success saving language properties '%s' (%s)", __CLASS__, __FUNCTION__, $language_name, $language_key));
         $this->languages = $LANGUAGE->retrieve_languages(TRUE);
         // TRUE means force reread from database after edit
     }
     $this->languages_overview();
 }