/** * Get tempcode for a adding/editing form. * * @param ?integer The workflow being edited (NULL: adding, not editing) * @return array A pair: The input fields, Hidden fields */ function get_form_fields($id = NULL) { ///////////////////////////////// // Get all of our requirements // ///////////////////////////////// require_code('form_templates'); require_lang('workflows'); require_code('workflows'); // These will hold our form elements, visible & hidden $fields = new ocp_tempcode(); $hidden = new ocp_tempcode(); // Grab all of the data we can about this workflow // Make some assumptions first $default = false; $approval_points = array(); $name_string = ''; // Now overwrite those assumptions if they're wrong if (!is_null($id)) { // We can grab the name straight away $name_string = get_translated_text($id); // Now see if we're the default $default_workflow = get_default_workflow(); if (!is_null($default_workflow) && $id == $default_workflow) { $default = true; } // Get the approval points in workflow order $approval_points = array_map('get_translated_text', get_requirements_for_workflow($id)); } else { // -1 indicates an invalid ID $id = -1; } //////////////////// // Build the form // //////////////////// // First we must be given a name (defaulting to the given name if // it has been passed). We want to show the user which names are // unavailable, so scrape the database for this information. $workflow_names = get_all_workflows(); $workflow_name_ids = array_keys($workflow_names); if (count($workflow_names) > 0) { $defined_names = do_lang('DEFINED_WORKFLOWS', implode(', ', $workflow_names)); } else { $defined_names = do_lang('NO_DEFINED_WORKFLOWS'); } $fields->attach(form_input_line(do_lang_tempcode('NAME'), do_lang_tempcode('WORKFLOW_NAME_DESCRIPTION', $defined_names), 'name', $name_string, true)); // Now we must handle the ID for this workflow. The ID is an index // to the translation table, thus if it is NULL we can generate // one simply by adding the given name to the translation table // later. Otherwise we must update the language string for this // ID. Either way this must be handled during the form processing, // since we don't have access to the string until then. Since we // want ID to be a number we will set it to an invalid value (-1) // to indicate a NULL status. $hidden->attach(form_input_hidden('workflow_id', strval($id))); // Give the user a multiline text entry to specify the approval // points for this workflow. Not as elegant as it could be, but it // doesn't require Javascript. $all_points = get_all_approval_points(); // We need to display which points are available $points_text = array_values($all_points); // An array of approval point names (strings) if ($points_text == array()) { $points_list = do_lang('APPROVAL_POINTS_DESCRIPTION_EMPTY_LIST'); } else { $points_list = do_lang('APPROVAL_POINTS_DESCRIPTION_LIST', implode(', ', $points_text)); } // Now add the approval point lines $fields->attach(form_input_text(do_lang_tempcode('WORKFLOW_APPROVAL_POINTS'), do_lang_tempcode('APPROVAL_POINTS_DESCRIPTION', $points_list), 'points', implode("\n", $approval_points), true, NULL, true)); // Add an option to make this default $fields->attach(form_input_tick(do_lang('DEFAULT_WORKFLOW'), do_lang('DEFAULT_WORKFLOW_DESCRIPTION'), 'is_default', $default)); // Actions $fields2 = new ocp_tempcode(); $fields2->attach(do_template('FORM_SCREEN_FIELD_SPACER', array('TITLE' => do_lang_tempcode('ACTIONS')))); // Add an option to redefine the approval permissions $fields2->attach(form_input_tick(do_lang('REDEFINE_WORKFLOW_POINTS'), do_lang('REDEFINE_WORKFLOW_POINTS_DESC'), 'redefine_points', false)); return array($fields, $hidden, new ocp_tempcode(), '', false, '', $fields2); }
/** * Handler for workflow form submission. * * @return tempcode Either an error page or a success message */ function workflow_update_handler() { require_lang('workflows'); require_code('database'); $success_message = do_lang('APPROVAL_UNCHANGED'); ///////////////////////////////////////// // Grab everything we need from $_POST // ///////////////////////////////////////// $workflow_id = post_param('workflow_id'); $content_id = post_param('content_id'); $workflow_notes = post_param('workflow_notes'); // Find out which approvals have been given $approvals = array(); foreach (get_requirements_for_workflow(post_param('workflow_id')) as $approval_id) { // We might not have a value for this point, since we may not have given a tick box for it if (array_key_exists('approval_' . strval($approval_id), $_POST)) { $approvals[$approval_id] = post_param_integer('approval_' . strval($approval_id)); } elseif (array_key_exists('tick_on_form__approval_' . strval($approval_id), $_POST)) { $approvals[$approval_id] = post_param_integer('tick_on_form__approval_' . strval($approval_id)); } } //////////////////////// // Get member details // //////////////////////// // Find out which groups/members to inform, starting with the // original submitter $send_to_members = array(); if (array_key_exists('send_author', $_POST)) { if (post_param_integer('send_author') == 1) { $submitter = get_submitter_of_workflow_content($content_id); if (!is_null($submitter)) { $send_to_members[$submitter] = 1; } } } // Now get the groups $group_ids = array(); // Only remember 1 copy of each group foreach (get_requirements_for_workflow($workflow_id) as $requirement) { foreach (get_groups_for_point($requirement) as $group) { if (!in_array($group, $group_ids)) { if (post_param_integer('send_' . strval($group), 0) == 1) { $group_ids[] = $group; } } } } // From the groups we can get the members, and from there the emails foreach ($GLOBALS['FORUM_DRIVER']->member_group_query($group_ids) as $member) { $send_to_members[$member['id']] = 1; } //////////////////////////////////////////// // Now play with the database as required // //////////////////////////////////////////// // See which values need updating (ie. approvals have been given/withdrawn) $updated_approvals = array(); $all_approval_statuses = array(); // Grab each point's status from the database $old_values = $GLOBALS['SITE_DB']->query_select('workflow_content_status', array('workflow_approval_name', 'status_code'), array('workflow_content_id' => $content_id)); $accounted_for_statuses = array(); // Look for any differences we need to make foreach ($old_values as $old_value) { $noted = false; // Keep a note of each value for including in emails // Only compare against values which we've been given if (array_key_exists($old_value['workflow_approval_name'], $approvals)) { $accounted_for_statuses[] = $old_value['workflow_approval_name']; // See if the database entry is the same as the given status if ($old_value['status_code'] != $approvals[$old_value['workflow_approval_name']]) { // If not then see if we have permission to change it $members_with_permission = array(); foreach ($GLOBALS['FORUM_DRIVER']->member_group_query(get_groups_for_point($old_value['workflow_approval_name'])) as $permitted) { $members_with_permission[] = $permitted['id']; } if (in_array(get_member(), $members_with_permission)) { // Remember that this needs to be changed $updated_approvals[$old_value['workflow_approval_name']] = $approvals[$old_value['workflow_approval_name']]; // Make a note of this value in the array of all statuses $all_approval_statuses[$old_value['workflow_approval_name']] = $approvals[$old_value['workflow_approval_name']]; $noted = true; } } } if (!$noted) { // If we're here then this status has either not been passed or // it does not need modifying. Either way we can grab a valid // status from the database. $all_approval_statuses[$old_value['workflow_approval_name']] = $old_value['status_code']; } } // Now add any unaccounted-for points to those which need updating $new_approvals = array(); foreach (array_keys($approvals) as $a) { if (!in_array($a, $accounted_for_statuses)) { $updated_approvals[$a] = $approvals[$a]; $new_approvals[] = $a; } } // Now we know which fields to update, let's do so foreach ($updated_approvals as $approval_id => $status_code) { $success_message = do_lang('APPROVAL_CHANGED_DESCRIPTION'); if (in_array($approval_id, $new_approvals)) { $GLOBALS['SITE_DB']->query_insert('workflow_content_status', array('status_code' => $status_code, 'approved_by' => get_member(), 'workflow_content_id' => $content_id, 'workflow_approval_name' => $approval_id)); } else { $GLOBALS['SITE_DB']->query_update('workflow_content_status', array('status_code' => $status_code, 'approved_by' => get_member()), array('workflow_content_id' => $content_id, 'workflow_approval_name' => $approval_id), '', 1, NULL, false, false); } } // Update the notes (this is done destructively, but is simplest) // We append a timestamped log of the action taken $notes_approved = array(); $notes_disapproved = array(); foreach ($updated_approvals as $approval_id => $status_code) { if ($status_code) { $notes_approved[] = $approval_id; } else { // Just because it's not approved, doesn't mean that it was unticked. // It may have just been added to the workflow. if (!in_array($approval_id, $new_approvals)) { $notes_disapproved[] = $approval_id; } } } if (count($notes_approved) + count($notes_disapproved) > 0) { $note_title = date('Y-m-d H:i') . ' ' . $GLOBALS['FORUM_DRIVER']->get_username(get_member()); $workflow_notes = $workflow_notes . "\n\n" . $note_title . "\n" . str_repeat('-', strlen($note_title)); $notes_approved = array_map('get_translated_text', $notes_approved); $notes_disapproved = array_map('get_translated_text', $notes_disapproved); if (count($notes_approved) > 0) { $workflow_notes .= "\n" . do_lang('WORKFLOW_APPROVED') . ': ' . implode(', ', $notes_approved); } if (count($notes_disapproved) > 0) { $workflow_notes .= "\n" . do_lang('WORKFLOW_DISAPPROVED') . ': ' . implode(', ', $notes_disapproved); } } $GLOBALS['SITE_DB']->query_update('workflow_content', array('notes' => $workflow_notes), array('id' => $content_id), '', 1); ///////////////////////////// // See if we're going live // ///////////////////////////// // Validation is stored, for the most part, in a 'validated' field // of the content's table. Those which store it elsewhere must // specify this via their content-meta-aware info. // Grab lookup data from the workflows database $content_details = $GLOBALS['SITE_DB']->query_select('workflow_content', array('source_type', 'source_id'), array('id' => $content_id), '', 1); if ($content_details == array()) { warn_exit(do_lang_tempcode('_MISSING_RESOURCE', 'workflow_content->' . strval($content_id))); } // Now use it to find this content's validation field $hooks = find_all_hooks('systems', 'content_meta_aware'); $found = false; // Guilty until proven innocent again foreach (array_keys($hooks) as $hook) { // Skip if this is not the hook we're after if ($hook != $content_details[0]['source_type']) { continue; } // Otherwise load and instantiate the hook require_code('hooks/systems/content_meta_aware/' . filter_naughty_harsh($hook)); $ob = object_factory('Hook_content_meta_aware_' . filter_naughty_harsh($hook), true); if (is_null($ob)) { continue; } // Bail out if the hook fails // Grab information about the hook $info = $ob->info(); $content_table = $info['table']; $content_field = $info['id_field']; if (array_key_exists('validated_field', $info)) { $content_validated_field = $info['validated_field']; } else { // Fall back to 'validated' if nothing is specified $content_validated_field = 'validated'; } if ($info['id_field_numeric']) { // If so then flag it with a shorter name, and use a different // name for the converted ID (ocPortal avoids dynamic typing) $numeric = true; $numeric_id = intval($content_details[0]['source_id']); // Errors arise from passing an object, but should be noticed by type checker } else { // Otherwise set the flag as false. We've already got a string. $numeric = false; } } // Now we have the details required to lookup this entry, wherever it // is. Let's get its current validation status and compare to what // the workflow would have it be if ($numeric) { $content_is_validated = $GLOBALS['SITE_DB']->query_select($content_table, array($content_validated_field), array($content_field => $numeric_id), '', 1); } else { $content_is_validated = $GLOBALS['SITE_DB']->query_select($content_table, array($content_validated_field), array($content_field => $content_details[0]['source_id']), '', 1); } // Make sure we've actually found something if ($content_is_validated == array()) { $source_id = $content_details[0]['source_id']; $validated_field = $source_id->content_validated_field; warn_exit(do_lang_tempcode('_MISSING_RESOURCE', $content_table . '->' . $content_field . '->' . $validated_field)); } // In order for content to go live all points must be approved // See if all points have been approved. If so, none will have // status 0 $all_points_approved = false; if ($GLOBALS['SITE_DB']->query_select('workflow_content_status', array('workflow_approval_name'), array('workflow_content_id' => $content_id, 'status_code' => 0)) == array()) { $all_points_approved = true; } // We need to act if the validation status is different to the total // completion of the workflow if (($content_is_validated[0][$content_validated_field] == 1) != $all_points_approved) { $success_message = $all_points_approved ? do_lang('APPROVAL_COMPLETE') : do_lang('APPROVAL_REVOKED'); $GLOBALS['SITE_DB']->query_update($content_table, array($content_validated_field => $all_points_approved ? 1 : 0), array($content_field => $content_details[0]['source_id']), '', 1); } /////////////////////////////////////////// // Now inform members about this content // /////////////////////////////////////////// // Make a nicely formatted list of the statuses $status_list = ''; foreach ($all_approval_statuses as $point => $status) { $status_list .= get_translated_text($point) . ': '; $status_list .= $status == 1 ? 'approved' : 'not approved'; $status_list .= ', '; } // At last we can send the email require_code('notifications'); if (count($send_to_members) > 0) { $success_message .= do_lang('APPROVAL_CHANGED_NOTIFICATIONS'); } //require_code('developer_tools'); //inspect($emails); $subject = do_lang('APPROVAL_EMAIL_SUBJECT', NULL, NULL, NULL, get_site_default_lang()); $body = do_lang('APPROVAL_EMAIL_BODY', post_param('http_referer', ocp_srv('HTTP_REFERER')), $status_list, $workflow_notes, get_site_default_lang()); dispatch_notification('workflow_step', NULL, $subject, $body, $send_to_members); // Finally return a success message $return_url = strip_tags(post_param('return_url')); return redirect_screen(new ocp_tempcode(), $return_url, $success_message); }