/** 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('"%s" <%s>', 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(); }