/** * Copy all the files from one file area to another. * * @param file_storage $fs - The source context id * @param int $fromcontextid - The source context id * @param string $fromcomponent - The source component * @param string $fromfilearea - The source filearea * @param int $fromitemid - The source item id * @param int $tocontextid - The destination context id * @param string $tocomponent - The destination component * @param string $tofilearea - The destination filearea * @param int $toitemid - The destination item id * @return boolean */ private function copy_area_files(file_storage $fs, $fromcontextid, $fromcomponent, $fromfilearea, $fromitemid, $tocontextid, $tocomponent, $tofilearea, $toitemid) { $newfilerecord = new stdClass(); $newfilerecord->contextid = $tocontextid; $newfilerecord->component = $tocomponent; $newfilerecord->filearea = $tofilearea; $newfilerecord->itemid = $toitemid; if ($files = $fs->get_area_files($fromcontextid, $fromcomponent, $fromfilearea, $fromitemid)) { foreach ($files as $file) { if ($file->is_directory() and $file->get_filepath() === '/') { // We need a way to mark the age of each draft area. // By not copying the root dir we force it to be created // automatically with current timestamp. continue; } $newfile = $fs->create_file_from_storedfile($newfilerecord, $file); } } return true; }
/** * Do the actual processing of the uploaded file * @param string $saveas_filename name to give to the file * @param int $maxbytes maximum file size * @param mixed $types optional array of file extensions that are allowed or '*' for all * @param string $savepath optional path to save the file to * @param int $itemid optional the ID for this item within the file area * @param string $license optional the license to use for this file * @param string $author optional the name of the author of this file * @return object containing details of the file uploaded */ public function process_upload($saveas_filename, $maxbytes, $types = '*', $savepath = '/', $itemid = 0, $license = null, $author = '') { global $USER, $CFG; if ((is_array($types) and in_array('*', $types)) or $types == '*') { $this->mimetypes = '*'; } else { foreach ($types as $type) { $this->mimetypes[] = mimeinfo('type', $type); } } if ($license == null) { $license = $CFG->sitedefaultlicense; } $record = new stdClass(); $record->filearea = 'draft'; $record->component = 'user'; $record->filepath = $savepath; $record->itemid = $itemid; $record->license = $license; $record->author = $author; $context = get_context_instance(CONTEXT_USER, $USER->id); $elname = 'repo_upload_file'; $fs = get_file_storage(); $sm = get_string_manager(); if ($record->filepath !== '/') { $record->filepath = file_correct_filepath($record->filepath); } if (!isset($_FILES[$elname])) { throw new moodle_exception('nofile'); } if (!empty($_FILES[$elname]['error'])) { switch ($_FILES[$elname]['error']) { case UPLOAD_ERR_INI_SIZE: throw new moodle_exception('upload_error_ini_size', 'repository_upload'); break; case UPLOAD_ERR_FORM_SIZE: throw new moodle_exception('upload_error_form_size', 'repository_upload'); break; case UPLOAD_ERR_PARTIAL: throw new moodle_exception('upload_error_partial', 'repository_upload'); break; case UPLOAD_ERR_NO_FILE: throw new moodle_exception('upload_error_no_file', 'repository_upload'); break; case UPLOAD_ERR_NO_TMP_DIR: throw new moodle_exception('upload_error_no_tmp_dir', 'repository_upload'); break; case UPLOAD_ERR_CANT_WRITE: throw new moodle_exception('upload_error_cant_write', 'repository_upload'); break; case UPLOAD_ERR_EXTENSION: throw new moodle_exception('upload_error_extension', 'repository_upload'); break; default: throw new moodle_exception('nofile'); } } // scan the files, throws exception and deletes if virus found // this is tricky because clamdscan daemon might not be able to access the files $permissions = fileperms($_FILES[$elname]['tmp_name']); @chmod($_FILES[$elname]['tmp_name'], $CFG->filepermissions); self::antivir_scan_file($_FILES[$elname]['tmp_name'], $_FILES[$elname]['name'], true); @chmod($_FILES[$elname]['tmp_name'], $permissions); if (empty($saveas_filename)) { $record->filename = clean_param($_FILES[$elname]['name'], PARAM_FILE); } else { $ext = ''; $match = array(); $filename = clean_param($_FILES[$elname]['name'], PARAM_FILE); if (preg_match('/\.([a-z0-9]+)$/i', $filename, $match)) { if (isset($match[1])) { $ext = $match[1]; } } $ext = !empty($ext) ? $ext : ''; if (preg_match('#\.(' . $ext . ')$#i', $saveas_filename)) { // saveas filename contains file extension already $record->filename = $saveas_filename; } else { $record->filename = $saveas_filename . '.' . $ext; } } // Check the file has some non-null contents - usually an indication that a user has // tried to upload a folder by mistake if (!$this->check_valid_contents($_FILES[$elname]['tmp_name'])) { throw new moodle_exception('upload_error_invalid_file', 'repository_upload', '', $record->filename); } if ($this->mimetypes != '*') { // check filetype $filemimetype = file_storage::mimetype($_FILES[$elname]['tmp_name']); if (!in_array($filemimetype, $this->mimetypes)) { throw new moodle_exception('invalidfiletype', 'repository', '', get_mimetype_description(array('filename' => $_FILES[$elname]['name']))); } } if (empty($record->itemid)) { $record->itemid = 0; } if (($maxbytes!==-1) && (filesize($_FILES[$elname]['tmp_name']) > $maxbytes)) { throw new file_exception('maxbytes'); } $record->contextid = $context->id; $record->userid = $USER->id; $record->source = ''; if (repository::draftfile_exists($record->itemid, $record->filepath, $record->filename)) { $existingfilename = $record->filename; $unused_filename = repository::get_unused_filename($record->itemid, $record->filepath, $record->filename); $record->filename = $unused_filename; $stored_file = $fs->create_file_from_pathname($record, $_FILES[$elname]['tmp_name']); $event = array(); $event['event'] = 'fileexists'; $event['newfile'] = new stdClass; $event['newfile']->filepath = $record->filepath; $event['newfile']->filename = $unused_filename; $event['newfile']->url = moodle_url::make_draftfile_url($record->itemid, $record->filepath, $unused_filename)->out(false); $event['existingfile'] = new stdClass; $event['existingfile']->filepath = $record->filepath; $event['existingfile']->filename = $existingfilename; $event['existingfile']->url = moodle_url::make_draftfile_url($record->itemid, $record->filepath, $existingfilename)->out(false); return $event; } else { $stored_file = $fs->create_file_from_pathname($record, $_FILES[$elname]['tmp_name']); return array( 'url'=>moodle_url::make_draftfile_url($record->itemid, $record->filepath, $record->filename)->out(false), 'id'=>$record->itemid, 'file'=>$record->filename); } }
function email_credit($email_sender, $email_to, $email_cc, $email_bcc, $email_subject, $email_message) { log_debug("inc_credits", "Executing email_credit([options])"); // external dependency of Mail_Mime if (!@(include_once 'Mail.php')) { log_write("error", "inc_credits", "Unable to find Mail module required for sending email"); return 0; } if (!@(include_once 'Mail/mime.php')) { log_write("error", "inc_credits", "Unable to find Mail::Mime module required for sending email"); return 0; } /* Generate a PDF of the credit note and save to tmp file */ log_debug("inc_credits", "Generating credit note PDF for emailing"); // generate PDF $this->generate_pdf(); if (error_check()) { return 0; } // save to a temporary file $tmp_filename = file_generate_name("/tmp/credit_" . $this->data["code_credit"] . "", "pdf"); if (!($fhandle = fopen($tmp_filename, "w"))) { die("fatal error occured whilst writing to file {$tmp_filename}"); } fwrite($fhandle, $this->obj_pdf->output); fclose($fhandle); /* Email the credit note */ log_debug("inc_credits", "Sending email"); // fetch sender address // // users have the choice of sending as the company or as their own staff email address & name. // if ($email_sender == "user") { // send as the user $email_sender = "\"" . user_information("realname") . "\" <" . user_information("contact_email") . ">"; } else { // send as the system $email_sender = "\"" . sql_get_singlevalue("SELECT value FROM config WHERE name='COMPANY_NAME'") . "\" <" . sql_get_singlevalue("SELECT value FROM config WHERE name='COMPANY_CONTACT_EMAIL'") . ">"; } // prepare headers $mail_headers = array('From' => $email_sender, 'Subject' => $email_subject, 'Cc' => $email_cc, 'Bcc' => $email_bcc); $mail_mime = new Mail_mime("\n"); $mail_mime->setTXTBody($email_message); $mail_mime->addAttachment($tmp_filename, 'application/pdf'); $mail_body = $mail_mime->get(); $mail_headers = $mail_mime->headers($mail_headers); $mail =& Mail::factory('mail', "-f " . $GLOBALS["config"]["COMPANY_CONTACT_EMAIL"]); $status = $mail->send($email_to, $mail_headers, $mail_body); if (PEAR::isError($status)) { log_write("error", "inc_credits", "An error occured whilst attempting to send the email: " . $status->getMessage() . ""); } else { log_write("debug", "inc_credits", "Successfully sent email invoice"); /* Start SQL Transaction to post email to journal */ $sql_obj = new sql_query(); $sql_obj->trans_begin(); /* Mark the invoice as having been sent */ $sql_obj = new sql_query(); $sql_obj->string = "UPDATE account_" . $this->type . " SET date_sent='" . date("Y-m-d") . "', sentmethod='email' WHERE id='" . $this->id . "'"; $sql_obj->execute(); /* Add the email information to the journal, including attaching a copy of the generated PDF */ log_write("debug", "inc_credits", "Uploading PDF and email details to journal..."); // create journal entry $journal = new journal_process(); $journal->prepare_set_journalname("account_" . $this->type); $journal->prepare_set_customid($this->id); $journal->prepare_set_type("file"); $journal->prepare_set_title("EMAIL: {$email_subject}"); $data["content"] = NULL; $data["content"] .= "To:\t" . $email_to . "\n"; $data["content"] .= "Cc:\t" . $email_cc . "\n"; $data["content"] .= "Bcc:\t" . $email_bcc . "\n"; $data["content"] .= "From:\t" . $email_sender . "\n"; $data["content"] .= "\n"; $data["content"] .= $email_message; $data["content"] .= "\n"; $journal->prepare_set_content($data["content"]); $journal->action_update(); // create journal entry $journal->action_lock(); // lock it to prevent any changes to historical record of delivered email // upload PDF file as an attachement $file_obj = new file_storage(); $file_obj->data["type"] = "journal"; $file_obj->data["customid"] = $journal->structure["id"]; if (!$file_obj->action_update_file($tmp_filename)) { log_write("error", "inc_credits", "Unable to upload emailed PDF to journal entry"); } /* Commit */ if (error_check()) { $sql_obj->trans_rollback(); } else { $sql_obj->trans_commit(); } } // end if successful send // cleanup - remove the temporary files log_debug("inc_credits", "Performing cleanup - removing temporary file {$tmp_filename}"); unlink($tmp_filename); // return if (error_check()) { return 0; } else { return 1; } }
$strheading = get_string('test_unoconv', 'assignfeedback_editpdf'); $PAGE->navbar->add(get_string('administrationsite')); $PAGE->navbar->add(get_string('plugins', 'admin')); $PAGE->navbar->add(get_string('assignmentplugins', 'mod_assign')); $PAGE->navbar->add(get_string('feedbackplugins', 'mod_assign')); $PAGE->navbar->add(get_string('pluginname', 'assignfeedback_editpdf'), new moodle_url('/admin/settings.php', array('section' => 'assignfeedback_editpdf'))); $PAGE->navbar->add($strheading); $PAGE->set_heading($strheading); $PAGE->set_title($strheading); if ($sendpdf) { require_sesskey(); // Serve the generated test pdf. file_storage::send_test_pdf(); die; } $result = file_storage::test_unoconv_path(); switch ($result->status) { case file_storage::UNOCONVPATH_OK: $msg = $OUTPUT->notification(get_string('test_unoconvok', 'assignfeedback_editpdf'), 'success'); $pdflink = new moodle_url($PAGE->url, array('sendpdf' => 1, 'sesskey' => sesskey())); $msg .= html_writer::link($pdflink, get_string('test_unoconvdownload', 'assignfeedback_editpdf')); $msg .= html_writer::empty_tag('br'); break; case file_storage::UNOCONVPATH_ERROR: $msg = $OUTPUT->notification($result->message, 'warning'); break; default: $msg = $OUTPUT->notification(get_string("test_unoconv{$result->status}", 'assignfeedback_editpdf'), 'warning'); break; } $returl = new moodle_url('/admin/settings.php', array('section' => 'assignfeedback_editpdf'));
function action_delete() { log_debug("journal_process", "Executing action_delete()"); /* Start Transaction */ $sql_obj = new sql_query(); $sql_obj->trans_begin(); /* Delete files (if applicable) */ if ($this->structure["type"] == "file") { $file_obj = new file_storage(); $file_obj->data["type"] = "journal"; $file_obj->data["customid"] = $this->structure["id"]; $file_obj->load_data_bytype(); $file_obj->action_delete(); } /* Delete journal record */ $sql_obj->string = "DELETE FROM `journal` WHERE id='" . $this->structure["id"] . "' LIMIT 1"; $sql_obj->execute(); /* Commit */ if (error_check()) { log_write("error", "journal_process", "An error occured preventing the journal record from being deleted"); $sql_obj->trans_rollback(); return 0; } else { log_write("notification", "journal_process", "Journal record cleanly removed."); $sql_obj->trans_commit(); return 1; } return 0; }
/** * Repository method to serve file * * @param stored_file $storedfile * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours) * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin * @param array $options additional options affecting the file serving */ public function send_file($storedfile, $lifetime = 86400, $filter = 0, $forcedownload = false, array $options = null) { $reference = $storedfile->get_reference(); $params = file_storage::unpack_reference($reference); $filepath = clean_param($params['filepath'], PARAM_PATH); $filename = clean_param($params['filename'], PARAM_FILE); $contextid = clean_param($params['contextid'], PARAM_INT); $filearea = 'private'; $component = 'user'; $itemid = 0; $fs = get_file_storage(); $storedfile = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename); send_stored_file($storedfile, $lifetime, $filter, $forcedownload, $options); }
/** * Wrapper function useful for deleting an existing file (if present) just * before creating a new one. * * @param file_storage $fs File storage * @param array $filerecord File record in same format used to create file */ public static function delete_existing_file_record(file_storage $fs, array $filerecord) { if ($existing = $fs->get_file($filerecord['contextid'], $filerecord['component'], $filerecord['filearea'], $filerecord['itemid'], $filerecord['filepath'], $filerecord['filename'])) { $existing->delete(); } }
/** * Prepare file reference information * * @param string $source source of the file, returned by repository as 'source' and received back from user (not cleaned) * @return string file reference, ready to be stored */ public function get_file_reference($source) { if ($source && $this->has_moodle_files()) { $params = @json_decode(base64_decode($source), true); if (!$params && !in_array($this->get_typename(), array('recent', 'user', 'local', 'coursefiles'))) { // IMPORTANT! Since default format for moodle files was changed in the minor release as a security fix // we maintain an old code here in order not to break 3rd party repositories that deal // with moodle files. Repositories are strongly encouraged to be upgraded, see MDL-45616. // In Moodle 2.8 this fallback will be removed. $params = file_storage::unpack_reference($source, true); return file_storage::pack_reference($params); } if (!is_array($params) || empty($params['contextid'])) { throw new repository_exception('invalidparams', 'repository'); } $params = array( 'component' => empty($params['component']) ? '' : clean_param($params['component'], PARAM_COMPONENT), 'filearea' => empty($params['filearea']) ? '' : clean_param($params['filearea'], PARAM_AREA), 'itemid' => empty($params['itemid']) ? 0 : clean_param($params['itemid'], PARAM_INT), 'filename' => empty($params['filename']) ? null : clean_param($params['filename'], PARAM_FILE), 'filepath' => empty($params['filepath']) ? null : clean_param($params['filepath'], PARAM_PATH), 'contextid' => clean_param($params['contextid'], PARAM_INT) ); // Check if context exists. if (!context::instance_by_id($params['contextid'], IGNORE_MISSING)) { throw new repository_exception('invalidparams', 'repository'); } return file_storage::pack_reference($params); } return $source; }
/** * Hvp module upgrade function. * * @param string $oldversion The version we are upgrading from * @return bool Success */ function xmldb_hvp_upgrade($oldversion) { global $CFG, $DB; $dbman = $DB->get_manager(); if ($oldversion < 2016011300) { $table = new xmldb_table('hvp'); // Define field timecreated to be added to hvp. $timecreated = new xmldb_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'slug'); // Conditionally launch add field timecreated. if (!$dbman->field_exists($table, $timecreated)) { $dbman->add_field($table, $timecreated); } // Define field timemodified to be added to hvp. $timemodified = new xmldb_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'timecreated'); // Conditionally launch add field timemodified. if (!$dbman->field_exists($table, $timemodified)) { $dbman->add_field($table, $timemodified); } // Hvp savepoint reached. upgrade_mod_savepoint(true, 2016011300, 'hvp'); } if ($oldversion < 2016042500) { // Define table hvp_tmpfiles to be created. $table = new xmldb_table('hvp_tmpfiles'); // Adding fields to table hvp_tmpfiles. $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); // Adding keys to table hvp_tmpfiles. $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); // Conditionally launch create table for hvp_tmpfiles. if (!$dbman->table_exists($table)) { $dbman->create_table($table); } // Hvp savepoint reached. upgrade_mod_savepoint(true, 2016042500, 'hvp'); } if ($oldversion < 2016050600) { // Define table hvp_events to be created. $table = new xmldb_table('hvp_events'); // Adding fields to table hvp_events. $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); $table->add_field('user_id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); $table->add_field('created_at', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); $table->add_field('type', XMLDB_TYPE_CHAR, '63', null, XMLDB_NOTNULL, null, null); $table->add_field('sub_type', XMLDB_TYPE_CHAR, '63', null, XMLDB_NOTNULL, null, null); $table->add_field('content_id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); $table->add_field('content_title', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); $table->add_field('library_name', XMLDB_TYPE_CHAR, '127', null, XMLDB_NOTNULL, null, null); $table->add_field('library_version', XMLDB_TYPE_CHAR, '31', null, XMLDB_NOTNULL, null, null); // Adding keys to table hvp_events. $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); // Conditionally launch create table for hvp_events. if (!$dbman->table_exists($table)) { $dbman->create_table($table); } // Define table hvp_counters to be created. $table = new xmldb_table('hvp_counters'); // Adding fields to table hvp_counters. $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); $table->add_field('type', XMLDB_TYPE_CHAR, '63', null, XMLDB_NOTNULL, null, null); $table->add_field('library_name', XMLDB_TYPE_CHAR, '127', null, XMLDB_NOTNULL, null, null); $table->add_field('library_version', XMLDB_TYPE_CHAR, '31', null, XMLDB_NOTNULL, null, null); $table->add_field('num', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); // Adding keys to table hvp_counters. $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); // Adding indexes to table hvp_counters. $table->add_index('realkey', XMLDB_INDEX_NOTUNIQUE, array('type', 'library_name', 'library_version')); // Conditionally launch create table for hvp_counters. if (!$dbman->table_exists($table)) { $dbman->create_table($table); } // Hvp savepoint reached. upgrade_mod_savepoint(true, 2016050600, 'hvp'); } if ($oldversion < 2016051000) { $table = new xmldb_table('hvp'); // Define field timecreated to be added to hvp. $intro = new xmldb_field('intro', XMLDB_TYPE_TEXT, null, null, null, null, null, 'name'); // Conditionally launch add field timecreated. if (!$dbman->field_exists($table, $intro)) { $dbman->add_field($table, $intro); } // Define field timemodified to be added to hvp. $introformat = new xmldb_field('introformat', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '0', 'intro'); // Conditionally launch add field timemodified. if (!$dbman->field_exists($table, $introformat)) { $dbman->add_field($table, $introformat); } // Hvp savepoint reached. upgrade_mod_savepoint(true, 2016051000, 'hvp'); } if ($oldversion < 2016110100) { // Change context of activity files from COURSE to MODULE. $filearea = 'content'; $component = 'mod_hvp'; // Find activity ID and correct context ID $hvpsresult = $DB->get_records_sql("SELECT f.id AS fileid, f.itemid, c.id, f.filepath, f.filename, f.pathnamehash\n FROM {files} f\n JOIN {course_modules} cm ON f.itemid = cm.instance\n JOIN {modules} md ON md.id = cm.module\n JOIN {context} c ON c.instanceid = cm.id\n WHERE md.name = 'hvp'\n AND f.filearea = 'content'\n AND c.contextlevel = " . CONTEXT_MODULE); foreach ($hvpsresult as $hvp) { // Need to re-hash pathname after changing context $pathnamehash = file_storage::get_pathname_hash($hvp->id, $component, $filearea, $hvp->itemid, $hvp->filepath, $hvp->filename); // Double check that hash doesn't exist (avoid duplicate entries) if (!$DB->get_field_sql("SELECT contextid FROM {files} WHERE pathnamehash = '{$pathnamehash}'")) { // Update context ID and pathname hash for files $DB->execute("UPDATE {files} SET contextid = {$hvp->id}, pathnamehash = '{$pathnamehash}' WHERE pathnamehash = '{$hvp->pathnamehash}'"); } } // Hvp savepoint reached. upgrade_mod_savepoint(true, 2016110100, 'hvp'); } return true; }
/** * Repository method to serve file * * @param stored_file $storedfile * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours) * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin * @param array $options additional options affecting the file serving */ public function send_file($storedfile, $lifetime = 86400, $filter = 0, $forcedownload = false, array $options = null) { $fs = get_file_storage(); $reference = $storedfile->get_reference(); $params = file_storage::unpack_reference($reference); $filename = is_null($params['filename']) ? null : clean_param($params['filename'], PARAM_FILE); $filepath = is_null($params['filepath']) ? null : clean_param($params['filepath'], PARAM_PATH); $contextid = is_null($params['contextid']) ? null : clean_param($params['contextid'], PARAM_INT); // hard coded file area and component for security $srcfile = $fs->get_file($contextid, 'course', 'legacy', 0, $filepath, $filename); send_stored_file($srcfile, $lifetime, $filter, $forcedownload, $options); }
/* Load Data */ $obj_rate_table = new cdr_rate_table(); $obj_rate_table->id = @security_form_input_predefined("int", "id_rate_table", 1, ""); $cdr_import_mode = @security_form_input_predefined("any", "cdr_rate_import_mode", 1, ""); /* Verify valid rate table */ if (!$obj_rate_table->verify_id()) { log_write("error", "process", "The CDR rate table you have attempted to edit - " . $obj_rate_table->id . " - does not exist in this system."); } /* Verify File Upload */ $file_obj = new file_storage(); $file_obj->verify_upload_form("cdr_rate_import_file", array("csv", "zip")); /* Handle Errors */ if (error_check()) { header("Location: ../index.php?page=services/cdr-rates-import.php&id=" . $obj_rate_table->id); exit(0); } else { error_clear(); /* Load the file */ $rate_table = array(); switch (format_file_extension($_FILES["cdr_rate_import_file"]["name"])) { case "zip":
bankstatement-process.php access: "accounts_import_statement" group members Validates the uploaded statement file as being a supported format and reads in the data into session information to pass to the column assignment and then the record matching pages. */ //inclues require "../../include/config.php"; require "../../include/amberphplib/main.php"; if (user_permissions_get("accounts_import_statement")) { /* Process Uploaded File */ $file_obj = new file_storage(); $file_obj->verify_upload_form("BANK_STATEMENT", array("csv")); $dest_account = @security_form_input_predefined("int", "dest_account", 1, ""); $employeeid = @security_form_input_predefined("any", "employeeid", 1, ""); /* Check for obvious errors */ if (error_check()) { header("Location: ../../index.php?page=accounts/import/bankstatement.php"); exit(0); } /* Import File Contents */ // declare array $transactions = array();
/** * name: __construct * params: */ public function __construct($dirs) { global $GV; parent::__construct($dirs, 'seminar', array('title', 'time')); }
/** * name: sort_by_field * params: */ protected function sort_by_field($data, $field_name) { self::$sort_field = $field_name; usort($data, array(__CLASS__, "sort_u")); return $data; }
/** * Determine if dir path exists or not in repository * * @param string $dirpath * @param stdclass $repository * @param boolean $encodepath * @param array $params * @return boolean true if dir path exists in repository, false otherwise */ function hotpot_pluginfile_dirpath_exists($dirpath, $repository, $encodepath, $params) { $dirs = explode('/', $dirpath); foreach ($dirs as $i => $dir) { $dirpath = implode('/', array_slice($dirs, 0, $i)); if ($encodepath) { $params['filepath'] = '/' . $dirpath . ($dirpath == '' ? '' : '/'); $params['filename'] = '.'; // "." signifies a directory $dirpath = file_storage::pack_reference($params); } $exists = false; $listing = $repository->get_listing($dirpath); foreach ($listing['list'] as $file) { if (empty($file['source'])) { if ($file['title'] == $dir) { $exists = true; break; } } } if (!$exists) { return false; } } // all dirs in path exist - success !! return true; }
/** * Do the actual processing of the uploaded file * @param string $saveas_filename name to give to the file * @param int $maxbytes maximum file size * @param mixed $types optional array of file extensions that are allowed or '*' for all * @param string $savepath optional path to save the file to * @param int $itemid optional the ID for this item within the file area * @param string $license optional the license to use for this file * @param string $author optional the name of the author of this file * @param bool $overwriteexisting optional user has asked to overwrite the existing file * @param int $areamaxbytes maximum size of the file area. * @return object containing details of the file uploaded */ public function process_upload($saveas_filename, $maxbytes, $types = '*', $savepath = '/', $itemid = 0, $license = null, $author = '', $overwriteexisting = false, $areamaxbytes = FILE_AREA_MAX_BYTES_UNLIMITED) { global $USER, $CFG; if (is_array($types) and in_array('*', $types) or $types == '*') { $this->mimetypes = '*'; } else { foreach ($types as $type) { $this->mimetypes[] = mimeinfo('type', $type); } } if ($license == null) { $license = $CFG->sitedefaultlicense; } $record = new stdClass(); $record->filearea = 'draft'; $record->component = 'user'; $record->filepath = $savepath; $record->itemid = $itemid; $record->license = $license; $record->author = $author; $context = context_user::instance($USER->id); $elname = 'repo_upload_file'; $fs = get_file_storage(); $sm = get_string_manager(); if ($record->filepath !== '/') { $record->filepath = file_correct_filepath($record->filepath); } if (!isset($_FILES[$elname])) { throw new moodle_exception('nofile'); } if (!empty($_FILES[$elname]['error'])) { switch ($_FILES[$elname]['error']) { case UPLOAD_ERR_INI_SIZE: throw new moodle_exception('upload_error_ini_size', 'repository_upload'); break; case UPLOAD_ERR_FORM_SIZE: throw new moodle_exception('upload_error_form_size', 'repository_upload'); break; case UPLOAD_ERR_PARTIAL: throw new moodle_exception('upload_error_partial', 'repository_upload'); break; case UPLOAD_ERR_NO_FILE: throw new moodle_exception('upload_error_no_file', 'repository_upload'); break; case UPLOAD_ERR_NO_TMP_DIR: throw new moodle_exception('upload_error_no_tmp_dir', 'repository_upload'); break; case UPLOAD_ERR_CANT_WRITE: throw new moodle_exception('upload_error_cant_write', 'repository_upload'); break; case UPLOAD_ERR_EXTENSION: throw new moodle_exception('upload_error_extension', 'repository_upload'); break; default: throw new moodle_exception('nofile'); } } \core\antivirus\manager::scan_file($_FILES[$elname]['tmp_name'], $_FILES[$elname]['name'], true); // {@link repository::build_source_field()} $sourcefield = $this->get_file_source_info($_FILES[$elname]['name']); $record->source = self::build_source_field($sourcefield); if (empty($saveas_filename)) { $record->filename = clean_param($_FILES[$elname]['name'], PARAM_FILE); } else { $ext = ''; $match = array(); $filename = clean_param($_FILES[$elname]['name'], PARAM_FILE); if (strpos($filename, '.') === false) { // File has no extension at all - do not add a dot. $record->filename = $saveas_filename; } else { if (preg_match('/\\.([a-z0-9]+)$/i', $filename, $match)) { if (isset($match[1])) { $ext = $match[1]; } } $ext = !empty($ext) ? $ext : ''; if (preg_match('#\\.(' . $ext . ')$#i', $saveas_filename)) { // saveas filename contains file extension already $record->filename = $saveas_filename; } else { $record->filename = $saveas_filename . '.' . $ext; } } } // Check the file has some non-null contents - usually an indication that a user has // tried to upload a folder by mistake if (!$this->check_valid_contents($_FILES[$elname]['tmp_name'])) { throw new moodle_exception('upload_error_invalid_file', 'repository_upload', '', $record->filename); } if ($this->mimetypes != '*') { // check filetype $filemimetype = file_storage::mimetype($_FILES[$elname]['tmp_name'], $record->filename); if (!in_array($filemimetype, $this->mimetypes)) { throw new moodle_exception('invalidfiletype', 'repository', '', get_mimetype_description(array('filename' => $_FILES[$elname]['name']))); } } if (empty($record->itemid)) { $record->itemid = 0; } if ($maxbytes !== -1 && filesize($_FILES[$elname]['tmp_name']) > $maxbytes) { $maxbytesdisplay = display_size($maxbytes); throw new file_exception('maxbytesfile', (object) array('file' => $record->filename, 'size' => $maxbytesdisplay)); } if (file_is_draft_area_limit_reached($record->itemid, $areamaxbytes, filesize($_FILES[$elname]['tmp_name']))) { throw new file_exception('maxareabytes'); } $record->contextid = $context->id; $record->userid = $USER->id; if (repository::draftfile_exists($record->itemid, $record->filepath, $record->filename)) { $existingfilename = $record->filename; $unused_filename = repository::get_unused_filename($record->itemid, $record->filepath, $record->filename); $record->filename = $unused_filename; $stored_file = $fs->create_file_from_pathname($record, $_FILES[$elname]['tmp_name']); if ($overwriteexisting) { repository::overwrite_existing_draftfile($record->itemid, $record->filepath, $existingfilename, $record->filepath, $record->filename); $record->filename = $existingfilename; } else { $event = array(); $event['event'] = 'fileexists'; $event['newfile'] = new stdClass(); $event['newfile']->filepath = $record->filepath; $event['newfile']->filename = $unused_filename; $event['newfile']->url = moodle_url::make_draftfile_url($record->itemid, $record->filepath, $unused_filename)->out(false); $event['existingfile'] = new stdClass(); $event['existingfile']->filepath = $record->filepath; $event['existingfile']->filename = $existingfilename; $event['existingfile']->url = moodle_url::make_draftfile_url($record->itemid, $record->filepath, $existingfilename)->out(false); return $event; } } else { $stored_file = $fs->create_file_from_pathname($record, $_FILES[$elname]['tmp_name']); } return array('url' => moodle_url::make_draftfile_url($record->itemid, $record->filepath, $record->filename)->out(false), 'id' => $record->itemid, 'file' => $record->filename); }
/** * Get a stored_file instance by filename * * @param string $filename * @return \stored_file */ public function get($filename) { return $this->storage->get_file($this->context->id, self::COMPONENT, self::FILEAREA, self::ITEMID, self::FILEPATH, $filename); }
/** * Performs synchronisation of an external file if the previous one has expired. * * This function must be implemented for external repositories supporting * FILE_REFERENCE, it is called for existing aliases when their filesize, * contenthash or timemodified are requested. It is not called for internal * repositories (see {@link repository::has_moodle_files()}), references to * internal files are updated immediately when source is modified. * * Referenced files may optionally keep their content in Moodle filepool (for * thumbnail generation or to be able to serve cached copy). In this * case both contenthash and filesize need to be synchronized. Otherwise repositories * should use contenthash of empty file and correct filesize in bytes. * * Note that this function may be run for EACH file that needs to be synchronised at the * moment. If anything is being downloaded or requested from external sources there * should be a small timeout. The synchronisation is performed to update the size of * the file and/or to update image and re-generated image preview. There is nothing * fatal if syncronisation fails but it is fatal if syncronisation takes too long * and hangs the script generating a page. * * Note: If you wish to call $file->get_filesize(), $file->get_contenthash() or * $file->get_timemodified() make sure that recursion does not happen. * * Called from {@link stored_file::sync_external_file()} * * @uses stored_file::set_missingsource() * @uses stored_file::set_synchronized() * @param stored_file $file * @return bool false when file does not need synchronisation, true if it was synchronised */ public function sync_reference(stored_file $file) { if ($file->get_repository_id() != $this->id) { // This should not really happen because the function can be called from stored_file only. return false; } if ($this->has_moodle_files()) { // References to local files need to be synchronised only once. // Later they will be synchronised automatically when the source is changed. if ($file->get_referencelastsync()) { return false; } $fs = get_file_storage(); $params = file_storage::unpack_reference($file->get_reference(), true); if (!is_array($params) || !($storedfile = $fs->get_file($params['contextid'], $params['component'], $params['filearea'], $params['itemid'], $params['filepath'], $params['filename']))) { $file->set_missingsource(); } else { $file->set_synchronized($storedfile->get_contenthash(), $storedfile->get_filesize(), 0, $storedfile->get_timemodified()); } return true; } return false; }
/** * What to do when this step is executed. */ protected function define_execution() { global $DB; $this->log('processing file aliases queue', backup::LOG_DEBUG); $fs = get_file_storage(); // Load the queue. $rs = $DB->get_recordset('backup_ids_temp', array('backupid' => $this->get_restoreid(), 'itemname' => 'file_aliases_queue'), '', 'info'); // Iterate over aliases in the queue. foreach ($rs as $record) { $info = backup_controller_dbops::decode_backup_temp_info($record->info); // Try to pick a repository instance that should serve the alias. $repository = $this->choose_repository($info); if (is_null($repository)) { $this->notify_failure($info, 'unable to find a matching repository instance'); continue; } if ($info->oldfile->repositorytype === 'local' or $info->oldfile->repositorytype === 'coursefiles') { // Aliases to Server files and Legacy course files may refer to a file // contained in the backup file or to some existing file (if we are on the // same site). try { $reference = file_storage::unpack_reference($info->oldfile->reference); } catch (Exception $e) { $this->notify_failure($info, 'invalid reference field format'); continue; } // Let's see if the referred source file was also included in the backup. $candidates = $DB->get_recordset('backup_files_temp', array('backupid' => $this->get_restoreid(), 'contextid' => $reference['contextid'], 'component' => $reference['component'], 'filearea' => $reference['filearea'], 'itemid' => $reference['itemid']), '', 'info, newcontextid, newitemid'); $source = null; foreach ($candidates as $candidate) { $candidateinfo = backup_controller_dbops::decode_backup_temp_info($candidate->info); if ($candidateinfo->filename === $reference['filename'] and $candidateinfo->filepath === $reference['filepath'] and !is_null($candidate->newcontextid) and !is_null($candidate->newitemid)) { $source = $candidateinfo; $source->contextid = $candidate->newcontextid; $source->itemid = $candidate->newitemid; break; } } $candidates->close(); if ($source) { // We have an alias that refers to another file also included in // the backup. Let us change the reference field so that it refers // to the restored copy of the original file. $reference = file_storage::pack_reference($source); // Send the new alias to the filepool. $fs->create_file_from_reference($info->newfile, $repository->id, $reference); $this->notify_success($info); continue; } else { // This is a reference to some moodle file that was not contained in the backup // file. If we are restoring to the same site, keep the reference untouched // and restore the alias as is if the referenced file exists. if ($this->task->is_samesite()) { if ($fs->file_exists($reference['contextid'], $reference['component'], $reference['filearea'], $reference['itemid'], $reference['filepath'], $reference['filename'])) { $reference = file_storage::pack_reference($reference); $fs->create_file_from_reference($info->newfile, $repository->id, $reference); $this->notify_success($info); continue; } else { $this->notify_failure($info, 'referenced file not found'); continue; } // If we are at other site, we can't restore this alias. } else { $this->notify_failure($info, 'referenced file not included'); continue; } } } else { if ($info->oldfile->repositorytype === 'user') { if ($this->task->is_samesite()) { // For aliases to user Private files at the same site, we have a chance to check // if the referenced file still exists. try { $reference = file_storage::unpack_reference($info->oldfile->reference); } catch (Exception $e) { $this->notify_failure($info, 'invalid reference field format'); continue; } if ($fs->file_exists($reference['contextid'], $reference['component'], $reference['filearea'], $reference['itemid'], $reference['filepath'], $reference['filename'])) { $reference = file_storage::pack_reference($reference); $fs->create_file_from_reference($info->newfile, $repository->id, $reference); $this->notify_success($info); continue; } else { $this->notify_failure($info, 'referenced file not found'); continue; } // If we are at other site, we can't restore this alias. } else { $this->notify_failure($info, 'restoring at another site'); continue; } } else { // This is a reference to some external file such as in boxnet or dropbox. // If we are restoring to the same site, keep the reference untouched and // restore the alias as is. if ($this->task->is_samesite()) { $fs->create_file_from_reference($info->newfile, $repository->id, $info->oldfile->reference); $this->notify_success($info); continue; // If we are at other site, we can't restore this alias. } else { $this->notify_failure($info, 'restoring at another site'); continue; } } } } $rs->close(); }
/** * Saves files from a draft file area to a real one (merging the list of files). * Can rewrite URLs in some content at the same time if desired. * * @category files * @global stdClass $USER * @param int $draftitemid the id of the draft area to use. Normally obtained * from file_get_submitted_draft_itemid('elementname') or similar. * @param int $contextid This parameter and the next two identify the file area to save to. * @param string $component * @param string $filearea indentifies the file area. * @param int $itemid helps identifies the file area. * @param array $options area options (subdirs=>false, maxfiles=-1, maxbytes=0) * @param string $text some html content that needs to have embedded links rewritten * to the @@PLUGINFILE@@ form for saving in the database. * @param bool $forcehttps force https urls. * @return string|null if $text was passed in, the rewritten $text is returned. Otherwise NULL. */ function file_save_draft_area_files($draftitemid, $contextid, $component, $filearea, $itemid, array $options = null, $text = null, $forcehttps = false) { global $USER; $usercontext = context_user::instance($USER->id); $fs = get_file_storage(); $options = (array) $options; if (!isset($options['subdirs'])) { $options['subdirs'] = false; } if (!isset($options['maxfiles'])) { $options['maxfiles'] = -1; // unlimited } if (!isset($options['maxbytes']) || $options['maxbytes'] == USER_CAN_IGNORE_FILE_SIZE_LIMITS) { $options['maxbytes'] = 0; // unlimited } if (!isset($options['areamaxbytes'])) { $options['areamaxbytes'] = FILE_AREA_MAX_BYTES_UNLIMITED; // Unlimited. } $allowreferences = true; if (isset($options['return_types']) && !($options['return_types'] & FILE_REFERENCE)) { // we assume that if $options['return_types'] is NOT specified, we DO allow references. // this is not exactly right. BUT there are many places in code where filemanager options // are not passed to file_save_draft_area_files() $allowreferences = false; } // Check if the draft area has exceeded the authorised limit. This should never happen as validation // should have taken place before, unless the user is doing something nauthly. If so, let's just not save // anything at all in the next area. if (file_is_draft_area_limit_reached($draftitemid, $options['areamaxbytes'])) { return null; } $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id'); $oldfiles = $fs->get_area_files($contextid, $component, $filearea, $itemid, 'id'); // One file in filearea means it is empty (it has only top-level directory '.'). if (count($draftfiles) > 1 || count($oldfiles) > 1) { // we have to merge old and new files - we want to keep file ids for files that were not changed // we change time modified for all new and changed files, we keep time created as is $newhashes = array(); $filecount = 0; foreach ($draftfiles as $file) { if (!$options['subdirs'] && $file->get_filepath() !== '/') { continue; } if (!$allowreferences && $file->is_external_file()) { continue; } if (!$file->is_directory()) { if ($options['maxbytes'] and $options['maxbytes'] < $file->get_filesize()) { // oversized file - should not get here at all continue; } if ($options['maxfiles'] != -1 and $options['maxfiles'] <= $filecount) { // more files - should not get here at all continue; } $filecount++; } $newhash = $fs->get_pathname_hash($contextid, $component, $filearea, $itemid, $file->get_filepath(), $file->get_filename()); $newhashes[$newhash] = $file; } // Loop through oldfiles and decide which we need to delete and which to update. // After this cycle the array $newhashes will only contain the files that need to be added. foreach ($oldfiles as $oldfile) { $oldhash = $oldfile->get_pathnamehash(); if (!isset($newhashes[$oldhash])) { // delete files not needed any more - deleted by user $oldfile->delete(); continue; } $newfile = $newhashes[$oldhash]; // Now we know that we have $oldfile and $newfile for the same path. // Let's check if we can update this file or we need to delete and create. if ($newfile->is_directory()) { // Directories are always ok to just update. } else { if (($source = @unserialize($newfile->get_source())) && isset($source->original)) { // File has the 'original' - we need to update the file (it may even have not been changed at all). $original = file_storage::unpack_reference($source->original); if ($original['filename'] !== $oldfile->get_filename() || $original['filepath'] !== $oldfile->get_filepath()) { // Very odd, original points to another file. Delete and create file. $oldfile->delete(); continue; } } else { // The same file name but absence of 'original' means that file was deteled and uploaded again. // By deleting and creating new file we properly manage all existing references. $oldfile->delete(); continue; } } // status changed, we delete old file, and create a new one if ($oldfile->get_status() != $newfile->get_status()) { // file was changed, use updated with new timemodified data $oldfile->delete(); // This file will be added later continue; } // Updated author if ($oldfile->get_author() != $newfile->get_author()) { $oldfile->set_author($newfile->get_author()); } // Updated license if ($oldfile->get_license() != $newfile->get_license()) { $oldfile->set_license($newfile->get_license()); } // Updated file source // Field files.source for draftarea files contains serialised object with source and original information. // We only store the source part of it for non-draft file area. $newsource = $newfile->get_source(); if ($source = @unserialize($newfile->get_source())) { $newsource = $source->source; } if ($oldfile->get_source() !== $newsource) { $oldfile->set_source($newsource); } // Updated sort order if ($oldfile->get_sortorder() != $newfile->get_sortorder()) { $oldfile->set_sortorder($newfile->get_sortorder()); } // Update file timemodified if ($oldfile->get_timemodified() != $newfile->get_timemodified()) { $oldfile->set_timemodified($newfile->get_timemodified()); } // Replaced file content if (!$oldfile->is_directory() && ($oldfile->get_contenthash() != $newfile->get_contenthash() || $oldfile->get_filesize() != $newfile->get_filesize() || $oldfile->get_referencefileid() != $newfile->get_referencefileid() || $oldfile->get_userid() != $newfile->get_userid())) { $oldfile->replace_file_with($newfile); } // unchanged file or directory - we keep it as is unset($newhashes[$oldhash]); } // Add fresh file or the file which has changed status // the size and subdirectory tests are extra safety only, the UI should prevent it foreach ($newhashes as $file) { $file_record = array('contextid' => $contextid, 'component' => $component, 'filearea' => $filearea, 'itemid' => $itemid, 'timemodified' => time()); if ($source = @unserialize($file->get_source())) { // Field files.source for draftarea files contains serialised object with source and original information. // We only store the source part of it for non-draft file area. $file_record['source'] = $source->source; } if ($file->is_external_file()) { $repoid = $file->get_repository_id(); if (!empty($repoid)) { $file_record['repositoryid'] = $repoid; $file_record['reference'] = $file->get_reference(); } } $fs->create_file_from_storedfile($file_record, $file); } } // note: do not purge the draft area - we clean up areas later in cron, // the reason is that user might press submit twice and they would loose the files, // also sometimes we might want to use hacks that save files into two different areas if (is_null($text)) { return null; } else { return file_rewrite_urls_to_pluginfile($text, $draftitemid, $forcehttps); } }
/** * Performs synchronisation of an external file if the previous one has expired. * * This function must be implemented for external repositories supporting * FILE_REFERENCE, it is called for existing aliases when their filesize, * contenthash or timemodified are requested. It is not called for internal * repositories (see {@link repository::has_moodle_files()}), references to * internal files are updated immediately when source is modified. * * Referenced files may optionally keep their content in Moodle filepool (for * thumbnail generation or to be able to serve cached copy). In this * case both contenthash and filesize need to be synchronized. Otherwise repositories * should use contenthash of empty file and correct filesize in bytes. * * Note that this function may be run for EACH file that needs to be synchronised at the * moment. If anything is being downloaded or requested from external sources there * should be a small timeout. The synchronisation is performed to update the size of * the file and/or to update image and re-generated image preview. There is nothing * fatal if syncronisation fails but it is fatal if syncronisation takes too long * and hangs the script generating a page. * * Note: If you wish to call $file->get_filesize(), $file->get_contenthash() or * $file->get_timemodified() make sure that recursion does not happen. * * Called from {@link stored_file::sync_external_file()} * * @uses stored_file::set_missingsource() * @uses stored_file::set_synchronized() * @param stored_file $file * @return bool false when file does not need synchronisation, true if it was synchronised */ public function sync_reference(stored_file $file) { if ($file->get_repository_id() != $this->id) { // This should not really happen because the function can be called from stored_file only. return false; } if ($this->has_moodle_files()) { // References to local files need to be synchronised only once. // Later they will be synchronised automatically when the source is changed. if ($file->get_referencelastsync()) { return false; } $fs = get_file_storage(); $params = file_storage::unpack_reference($file->get_reference(), true); if (!is_array($params) || !($storedfile = $fs->get_file($params['contextid'], $params['component'], $params['filearea'], $params['itemid'], $params['filepath'], $params['filename']))) { $file->set_missingsource(); } else { $file->set_synchronized($storedfile->get_contenthash(), $storedfile->get_filesize()); } return true; } // Backward compatibility (Moodle 2.3-2.5) implementation that calls // methods repository::get_reference_file_lifetime(), repository::sync_individual_file() // and repository::get_file_by_reference(). These methods are removed from the // base repository class but may still be implemented by the child classes. // THIS IS NOT A GOOD EXAMPLE of implementation. For good examples see the overwriting methods. if (!method_exists($this, 'get_file_by_reference')) { // Function get_file_by_reference() is not implemented. No synchronisation. return false; } // Check if the previous sync result is still valid. if (method_exists($this, 'get_reference_file_lifetime')) { $lifetime = $this->get_reference_file_lifetime($file->get_reference()); } else { // Default value that was hardcoded in Moodle 2.3 - 2.5. $lifetime = 60 * 60 * 24; } if (($lastsynced = $file->get_referencelastsync()) && $lastsynced + $lifetime >= time()) { return false; } $cache = cache::make('core', 'repositories'); if (($lastsyncresult = $cache->get('sync:'.$file->get_referencefileid())) !== false) { if ($lastsyncresult === true) { // We are in the process of synchronizing this reference. // Avoid recursion when calling $file->get_filesize() and $file->get_contenthash(). return false; } else { // We have synchronised the same reference inside this request already. // It looks like the object $file was created before the synchronisation and contains old data. if (!empty($lastsyncresult['missing'])) { $file->set_missingsource(); } else { $cache->set('sync:'.$file->get_referencefileid(), true); if ($file->get_contenthash() != $lastsyncresult['contenthash'] || $file->get_filesize() != $lastsyncresult['filesize']) { $file->set_synchronized($lastsyncresult['contenthash'], $lastsyncresult['filesize']); } $cache->set('sync:'.$file->get_referencefileid(), $lastsyncresult); } return true; } } // Weird function sync_individual_file() that was present in API in 2.3 - 2.5, default value was true. if (method_exists($this, 'sync_individual_file') && !$this->sync_individual_file($file)) { return false; } // Set 'true' into the cache to indicate that file is in the process of synchronisation. $cache->set('sync:'.$file->get_referencefileid(), true); // Create object with the structure that repository::get_file_by_reference() expects. $reference = new stdClass(); $reference->id = $file->get_referencefileid(); $reference->reference = $file->get_reference(); $reference->referencehash = sha1($file->get_reference()); $reference->lastsync = $file->get_referencelastsync(); $reference->lifetime = $lifetime; $fileinfo = $this->get_file_by_reference($reference); $contenthash = null; $filesize = null; $fs = get_file_storage(); if (!empty($fileinfo->filesize)) { // filesize returned if (!empty($fileinfo->contenthash) && $fs->content_exists($fileinfo->contenthash)) { // contenthash is specified and valid $contenthash = $fileinfo->contenthash; } else if ($fileinfo->filesize == $file->get_filesize()) { // we don't know the new contenthash but the filesize did not change, // assume the contenthash did not change either $contenthash = $file->get_contenthash(); } else { // we can't save empty contenthash so generate contenthash from empty string list($contenthash, $unused1, $unused2) = $fs->add_string_to_pool(''); } $filesize = $fileinfo->filesize; } else if (!empty($fileinfo->filepath)) { // File path returned list($contenthash, $filesize, $newfile) = $fs->add_file_to_pool($fileinfo->filepath); } else if (!empty($fileinfo->handle) && is_resource($fileinfo->handle)) { // File handle returned $contents = ''; while (!feof($fileinfo->handle)) { $contents .= fread($fileinfo->handle, 8192); } fclose($fileinfo->handle); list($contenthash, $filesize, $newfile) = $fs->add_string_to_pool($contents); } else if (isset($fileinfo->content)) { // File content returned list($contenthash, $filesize, $newfile) = $fs->add_string_to_pool($fileinfo->content); } if (!isset($contenthash) or !isset($filesize)) { $file->set_missingsource(null); $cache->set('sync:'.$file->get_referencefileid(), array('missing' => true)); } else { // update files table $file->set_synchronized($contenthash, $filesize); $cache->set('sync:'.$file->get_referencefileid(), array('contenthash' => $contenthash, 'filesize' => $filesize)); } return true; }
/** * Prepare file reference information * * @param string $source * @return string file referece */ public function get_file_reference($source) { if ($this->has_moodle_files() && $this->supported_returntypes() & FILE_REFERENCE) { $params = file_storage::unpack_reference($source); if (!is_array($params)) { throw new repository_exception('invalidparams', 'repository'); } return file_storage::pack_reference($params); } return $source; }
} else { // make sure the custom ID of the file matches the journal ID if ($customid != $journalid) { $_SESSION["error"]["message"][] = "Error: File customid and journal ID do not match"; } else { // make sure the journal entry belongs to a user $userid = sql_get_singlevalue("SELECT customid as value FROM journal WHERE journalname='users' AND id='{$journalid}'"); if (!$userid) { $_SESSION["error"]["message"][] = "Unable to match the provided journal entry to a user journal."; } } } /* Produce output - either output request file content, or take the user to a message page to view errors. */ if ($_SESSION["error"]["message"]) { header("Location: ../index.php?page=message.php"); exit(0); } else { // output file data $file_obj = new file_storage(); $file_obj->id = $fileid; $file_obj->load_data(); $file_obj->filedata_render(); } } else { error_render_noperms(); header("Location: ../index.php?page=message.php"); exit(0); }
public function test_search_references() { $user = $this->setup_three_private_files(); $fs = get_file_storage(); $repos = repository::get_instances(array('type' => 'user')); $repo = reset($repos); $alias1 = array('contextid' => $user->ctxid, 'component' => 'user', 'filearea' => 'private', 'itemid' => 0, 'filepath' => '/aliases/', 'filename' => 'alias-to-1.txt'); $alias2 = array('contextid' => $user->ctxid, 'component' => 'user', 'filearea' => 'private', 'itemid' => 0, 'filepath' => '/aliases/', 'filename' => 'another-alias-to-1.txt'); $reference = file_storage::pack_reference(array('contextid' => $user->ctxid, 'component' => 'user', 'filearea' => 'private', 'itemid' => 0, 'filepath' => '/', 'filename' => '1.txt')); // There are no aliases now. $result = $fs->search_references($reference); $this->assertEquals(array(), $result); $result = $fs->search_references_count($reference); $this->assertSame($result, 0); // Create two aliases and make sure they are returned. $fs->create_file_from_reference($alias1, $repo->id, $reference); $fs->create_file_from_reference($alias2, $repo->id, $reference); $result = $fs->search_references($reference); $this->assertTrue(is_array($result)); $this->assertEquals(count($result), 2); foreach ($result as $alias) { $this->assertTrue($alias instanceof stored_file); } $result = $fs->search_references_count($reference); $this->assertSame($result, 2); // The method can't be used for references to files outside the filepool. $exceptionthrown = false; try { $fs->search_references('http://dl.dropbox.com/download/1234567/naked-dougiamas.jpg'); } catch (file_reference_exception $e) { $exceptionthrown = true; } $this->assertTrue($exceptionthrown); $exceptionthrown = false; try { $fs->search_references_count('http://dl.dropbox.com/download/1234567/naked-dougiamas.jpg'); } catch (file_reference_exception $e) { $exceptionthrown = true; } $this->assertTrue($exceptionthrown); }
/** * Replace the area files in the specified area with those in the source item id. * * @param \file_storage $fs The file storage class * @param int $contextid The ID of the context for the setaskment. * @param int $sourceitemid The itemid to copy from - typically the source grade id. * @param int $itemid The itemid to copy to - typically the target grade id. * @param string $area The file storage area. * @param bool $includesubdirs Whether to copy the content of sub-directories too. */ public static function replace_files_from_to($fs, $contextid, $sourceitemid, $itemid, $area, $includesubdirs = false) { $component = 'setaskfeedback_editpdf'; // Remove the existing files within this area. $fs->delete_area_files($contextid, $component, $area, $itemid); // Copy the files from the source area. if ($files = $fs->get_area_files($contextid, $component, $area, $sourceitemid, "filename", $includesubdirs)) { foreach ($files as $file) { $newrecord = new \stdClass(); $newrecord->contextid = $contextid; $newrecord->itemid = $itemid; $fs->create_file_from_storedfile($newrecord, $file); } } }
function prepare_add_file($fieldname, $file_extension, $file_type, $file_id) { log_debug("template_engine", "Executing prepare_add_file({$fieldname}, {$file_extension}, {$file_type}, {$file_id})"); $tmp_filename = file_generate_name("/tmp/{$fieldname}", $file_extension); // output file data $file_obj = new file_storage(); $file_obj->data["type"] = $file_type; $file_obj->data["customid"] = $file_id; if (!$file_obj->load_data_bytype()) { log_write("error", "template_engine", "Unable to find company logo image - use the administration config page to upload a company logo"); return 0; } $file_obj->filedata_write($tmp_filename); // work out the filename without path or extension // // we have to do this, since latex will not tolerate file extensions in the filename when // the file is referenced in the tex data. // preg_match("/\\S*\\/(\\S*).{$file_extension}\$/", $tmp_filename, $matches); $tmp_filename_short = $matches[1]; // map fieldname to filename $this->data_files[$fieldname]["filename"] = $tmp_filename; $this->data_files[$fieldname]["filename_short"] = $tmp_filename_short; log_debug("template_engine", "Wrote tempory file {$tmp_filename} with shortname of {$tmp_filename_short}"); return 1; }
/** * Check if unoconv configured path is correct and working. * * @return \stdClass an object with the test status and the UNOCONVPATH_ constant message. */ public static function test_unoconv_path() { global $CFG; $unoconvpath = $CFG->pathtounoconv; $ret = new \stdClass(); $ret->status = self::UNOCONVPATH_OK; $ret->message = null; if (empty($unoconvpath)) { $ret->status = self::UNOCONVPATH_EMPTY; return $ret; } if (!file_exists($unoconvpath)) { $ret->status = self::UNOCONVPATH_DOESNOTEXIST; return $ret; } if (is_dir($unoconvpath)) { $ret->status = self::UNOCONVPATH_ISDIR; return $ret; } if (!file_is_executable($unoconvpath)) { $ret->status = self::UNOCONVPATH_NOTEXECUTABLE; return $ret; } if (!\file_storage::can_convert_documents()) { $ret->status = self::UNOCONVPATH_VERSIONNOTSUPPORTED; return $ret; } return $ret; }
function email_invoice($email_sender, $email_to, $email_cc, $email_bcc, $email_subject, $email_message) { log_debug("invoice", "Executing email_invoice([options])"); // external dependency of Mail_Mime if (!@(include_once 'Mail.php')) { log_write("error", "invoice", "Unable to find Mail module required for sending email"); return 0; } if (!@(include_once 'Mail/mime.php')) { log_write("error", "invoice", "Unable to find Mail::Mime module required for sending email"); return 0; } // track attachment files to tidy up $file_attachments = array(); /* Prepare Email Mime Data & Headers */ // fetch sender address // // users have the choice of sending as the company or as their own staff email address & name. // if ($email_sender == "user") { // send as the user $email_sender = "\"" . user_information("realname") . "\" <" . user_information("contact_email") . ">"; } else { // send as the system $email_sender = "\"" . sql_get_singlevalue("SELECT value FROM config WHERE name='COMPANY_NAME'") . "\" <" . sql_get_singlevalue("SELECT value FROM config WHERE name='COMPANY_CONTACT_EMAIL'") . ">"; } // prepare headers $mail_headers = array('From' => $email_sender, 'Subject' => $email_subject, 'Cc' => $email_cc, 'Bcc' => $email_bcc); $mail_mime = new Mail_mime("\n"); $mail_mime->setTXTBody($email_message); /* Generate a PDF of the invoice and save to tmp file */ log_debug("invoice", "Generating invoice PDF for emailing"); // generate PDF $this->generate_pdf(); if (error_check()) { return 0; } // save to a temporary file if ($this->type == "ar") { $tmp_file_invoice = file_generate_name($GLOBALS["config"]["PATH_TMPDIR"] . "/invoice_" . $this->data["code_invoice"] . "", "pdf"); } else { $tmp_file_invoice = file_generate_name($GLOBALS["config"]["PATH_TMPDIR"] . "/quote_" . $this->data["code_quote"] . "", "pdf"); //$email_template = sql_get_singlevalue("SELECT value FROM config WHERE name IN('TEMPLATE_QUOTE_EMAIL') LIMIT 1"); } if (!($fhandle = fopen($tmp_file_invoice, "w"))) { log_write("error", "invoice", "A fatal error occured whilst writing invoice PDF to file {$tmp_file_invoice}, unable to send email"); return 0; } fwrite($fhandle, $this->obj_pdf->output); fclose($fhandle); // attach $mail_mime->addAttachment($tmp_file_invoice, 'application/pdf'); $file_attachments[] = $tmp_file_invoice; /* Fetch Extra Attachments Certain billing processes may add file attachments to the journal that should be sent along with the invoice when an email is generated. Here we grab those file attachments and send each one. */ $obj_sql_journal = new sql_query(); $obj_sql_journal->string = "SELECT id FROM journal WHERE journalname='account_ar' AND customid='" . $this->id . "' AND title LIKE 'SERVICE:%'"; $obj_sql_journal->execute(); if ($obj_sql_journal->num_rows()) { $obj_sql_journal->fetch_array(); foreach ($obj_sql_journal->data as $data_journal) { // there are journaled attachments to send // // we don't care about any of the journal data, we just need to pull the file attachment from // storage, write to disk and then attach to the email // // fetch file object $file_obj = new file_storage(); $file_obj->data["type"] = "journal"; $file_obj->data["customid"] = $data_journal["id"]; if (!$file_obj->load_data_bytype()) { log_write("error", "inc_invoices", "Unable to load file from journal to attach to invoice email - possible file storage issue?"); return 0; } $file_extension = format_file_extension($file_obj->data["file_name"]); $file_name = format_file_noextension($file_obj->data["file_name"]); $file_ctype = format_file_contenttype($file_extension); // we have to write the file to disk before attaching it $tmp_file_attach = file_generate_name($GLOBALS["config"]["PATH_TMPDIR"] . "/" . $file_name, $file_extension); if (!$file_obj->filedata_write($tmp_file_attach)) { log_write("error", "inc_invoices", "Unable to write file attachments from journal to tmp space"); return 0; } // add to the invoice $mail_mime->addAttachment($tmp_file_attach, $file_ctype); $file_attachments[] = $tmp_file_attach; // cleanup - tmp file will be removed ;ater unset($file_obj); } // end of for each journal item } // end if sendable journal items unset($obj_sql_journal); /* Email the invoice */ log_write("debug", "invoice", "Sending generated email...."); $mail_body = $mail_mime->get(); $mail_headers = $mail_mime->headers($mail_headers); $mail =& Mail::factory('mail', "-f " . $GLOBALS["config"]["COMPANY_CONTACT_EMAIL"]); $status = $mail->send($email_to, $mail_headers, $mail_body); if (PEAR::isError($status)) { log_write("error", "inc_invoice", "An error occured whilst attempting to send the email: " . $status->getMessage() . ""); } else { log_write("debug", "inc_invoice", "Successfully sent email invoice"); /* Start SQL Transaction to post email to journal */ $sql_obj = new sql_query(); $sql_obj->trans_begin(); /* Mark the invoice as having been sent */ $sql_obj = new sql_query(); $sql_obj->string = "UPDATE account_" . $this->type . " SET date_sent='" . date("Y-m-d") . "', sentmethod='email' WHERE id='" . $this->id . "'"; $sql_obj->execute(); /* Add the email information to the journal, including attaching a copy of the generated PDF */ log_write("debug", "inc_invoice", "Uploading PDF and email details to journal..."); // create journal entry $journal = new journal_process(); $journal->prepare_set_journalname("account_" . $this->type); $journal->prepare_set_customid($this->id); $journal->prepare_set_type("file"); $journal->prepare_set_title("EMAIL: {$email_subject}"); $data["content"] = NULL; $data["content"] .= "To:\t" . $email_to . "\n"; $data["content"] .= "Cc:\t" . $email_cc . "\n"; $data["content"] .= "Bcc:\t" . $email_bcc . "\n"; $data["content"] .= "From:\t" . $email_sender . "\n"; $data["content"] .= "\n"; $data["content"] .= $email_message; $data["content"] .= "\n"; $journal->prepare_set_content($data["content"]); $journal->action_update(); // create journal entry $journal->action_lock(); // lock it to prevent any changes to historical record of delivered email // upload PDF file as an attachement $file_obj = new file_storage(); $file_obj->data["type"] = "journal"; $file_obj->data["customid"] = $journal->structure["id"]; if (!$file_obj->action_update_file($tmp_file_invoice)) { log_write("error", "inc_invoice", "Unable to upload emailed PDF to journal entry"); } /* Commit */ if (error_check()) { $sql_obj->trans_rollback(); } else { $sql_obj->trans_commit(); } } // end if successful send // cleanup - remove the temporary files log_debug("inc_invoice", "Performing cleanup, removing temporary files used for emails"); foreach ($file_attachments as $filename) { log_debug("inc_invoice", "Removing tmp file {$filename}"); unlink($filename); } // return if (error_check()) { return 0; } else { return 1; } }
/** * Initialise a draft file area from a real one by copying the files. A draft * area will be created if one does not already exist. Normally you should * get $draftitemid by calling file_get_submitted_draft_itemid('elementname'); * * @category files * @global stdClass $CFG * @global stdClass $USER * @param int $draftitemid the id of the draft area to use, or 0 to create a new one, in which case this parameter is updated. * @param int $contextid This parameter and the next two identify the file area to copy files from. * @param string $component * @param string $filearea helps indentify the file area. * @param int $itemid helps identify the file area. Can be null if there are no files yet. * @param array $options text and file options ('subdirs'=>false, 'forcehttps'=>false) * @param string $text some html content that needs to have embedded links rewritten to point to the draft area. * @return string|null returns string if $text was passed in, the rewritten $text is returned. Otherwise NULL. */ function file_prepare_draft_area(&$draftitemid, $contextid, $component, $filearea, $itemid, array $options = null, $text = null) { global $CFG, $USER, $CFG; $options = (array) $options; if (!isset($options['subdirs'])) { $options['subdirs'] = false; } if (!isset($options['forcehttps'])) { $options['forcehttps'] = false; } $usercontext = context_user::instance($USER->id); $fs = get_file_storage(); if (empty($draftitemid)) { // create a new area and copy existing files into $draftitemid = file_get_unused_draft_itemid(); $file_record = array('contextid' => $usercontext->id, 'component' => 'user', 'filearea' => 'draft', 'itemid' => $draftitemid); if (!is_null($itemid) and $files = $fs->get_area_files($contextid, $component, $filearea, $itemid)) { foreach ($files as $file) { if ($file->is_directory() and $file->get_filepath() === '/') { // we need a way to mark the age of each draft area, // by not copying the root dir we force it to be created automatically with current timestamp continue; } if (!$options['subdirs'] and ($file->is_directory() or $file->get_filepath() !== '/')) { continue; } $draftfile = $fs->create_file_from_storedfile($file_record, $file); // XXX: This is a hack for file manager (MDL-28666) // File manager needs to know the original file information before copying // to draft area, so we append these information in mdl_files.source field // {@link file_storage::search_references()} // {@link file_storage::search_references_count()} $sourcefield = $file->get_source(); $newsourcefield = new stdClass(); $newsourcefield->source = $sourcefield; $original = new stdClass(); $original->contextid = $contextid; $original->component = $component; $original->filearea = $filearea; $original->itemid = $itemid; $original->filename = $file->get_filename(); $original->filepath = $file->get_filepath(); $newsourcefield->original = file_storage::pack_reference($original); $draftfile->set_source(serialize($newsourcefield)); // End of file manager hack } } if (!is_null($text)) { // at this point there should not be any draftfile links yet, // because this is a new text from database that should still contain the @@pluginfile@@ links // this happens when developers forget to post process the text $text = str_replace("\"{$CFG->httpswwwroot}/draftfile.php", "\"{$CFG->httpswwwroot}/brokenfile.php#", $text); } } else { // nothing to do } if (is_null($text)) { return null; } // relink embedded files - editor can not handle @@PLUGINFILE@@ ! return file_rewrite_pluginfile_urls($text, 'draftfile.php', $usercontext->id, 'user', 'draft', $draftitemid, $options); }
function service_invoices_generate($customerid = NULL) { log_debug("inc_services_invoicegen", "Executing service_invoices_generate({$customerid})"); /* Invoice Report Statistics */ $invoice_stats = array(); $invoice_stats["time_start"] = time(); $invoice_stats["total"] = 0; $invoice_stats["total_failed"] = 0; /* Run through all the customers */ $sql_customers_obj = new sql_query(); $sql_customers_obj->string = "SELECT id, code_customer, name_customer FROM customers"; if ($customerid) { $sql_customers_obj->string .= " WHERE id='{$customerid}' LIMIT 1"; } $sql_customers_obj->execute(); if ($sql_customers_obj->num_rows()) { $sql_customers_obj->fetch_array(); foreach ($sql_customers_obj->data as $customer_data) { /* Fetch all periods belonging to this customer which need to be billed. */ $sql_periods_obj = new sql_query(); $sql_periods_obj->string = "SELECT " . "services_customers_periods.id, " . "services_customers_periods.rebill, " . "services_customers_periods.invoiceid, " . "services_customers_periods.invoiceid_usage, " . "services_customers_periods.date_start, " . "services_customers_periods.date_end, " . "services_customers.date_period_first, " . "services_customers.date_period_next, " . "services_customers.date_period_last, " . "services_customers.id as id_service_customer, " . "services_customers.serviceid " . "FROM services_customers_periods " . "LEFT JOIN services_customers ON services_customers.id = services_customers_periods.id_service_customer " . "WHERE " . "services_customers.customerid='" . $customer_data["id"] . "' " . "AND (invoiceid = '0' OR rebill = '1')" . "AND date_billed <= '" . date("Y-m-d") . "'"; $sql_periods_obj->execute(); if ($sql_periods_obj->num_rows()) { $sql_periods_obj->fetch_array(); /* BILL CUSTOMER This customer has at least one service that needs to be billed. We need to create a new invoice, and then process each service, adding the services to the invoice as items. */ /* Start Transaction (one transaction per invoice) */ $sql_obj = new sql_query(); $sql_obj->trans_begin(); /* Create new invoice */ $invoice = new invoice(); $invoice->type = "ar"; $invoice->prepare_code_invoice(); $invoice->data["customerid"] = $customer_data["id"]; $invoice->data["employeeid"] = 1; // set employee to the default internal user $invoice->prepare_date_shift(); if (!$invoice->action_create()) { log_write("error", "services_invoicegen", "Unexpected problem occured whilst attempting to create invoice."); $sql_obj->trans_rollback(); return 0; } $invoiceid = $invoice->id; $invoicecode = $invoice->data["code_invoice"]; unset($invoice); /* Create Service Items We need to create an item for basic service plan - IE: the regular fixed fee, and another item for any excess usage. */ foreach ($sql_periods_obj->data as $period_data) { /* TODO: We should be able to re-bill usage here when needed with a clever cheat - if we load the periods to be billed, we can then ignore the plan item, and rebill the usage item. */ $period_data["mode"] = "standard"; if ($period_data["rebill"]) { if (!$period_data["invoiceid_usage"] && $period_data["invoiceid"]) { // the selected period has been billed, but the usage has been flagged for rebilling - we need to *ignore* the base plan // item and only bill for the usage range. $period_data["mode"] = "rebill_usage"; } } // fetch service details $obj_service = new service_bundle(); $obj_service->option_type = "customer"; $obj_service->option_type_id = $period_data["id_service_customer"]; if (!$obj_service->verify_id_options()) { log_write("error", "customers_services", "Unable to verify service ID of " . $period_data["id_service_customer"] . " as being valid."); return 0; } $obj_service->load_data(); $obj_service->load_data_options(); // ratio is used to adjust prices for partial periods $ratio = 1; if ($obj_service->data["billing_mode_string"] == "monthend" || $obj_service->data["billing_mode_string"] == "monthadvance" || $obj_service->data["billing_mode_string"] == "monthtelco") { log_debug("services_invoicegen", "Invoice bills by month date"); /* Handle monthly billing Normally, monthly billing is easy, however the very first billing period is special, as it may span a more than 1 month, or consist of less than the full month, depending on the operational mode. SERVICE_PARTPERIOD_MODE == seporate If a service is started on 2008-01-09, the end date of the billing period will be 2008-01-31, which is less than one month - 22 days. We can calculate with: ( standard_cost / normal_month_num_days ) * num_days_in_partial_month == total_amount SERVICE_PARTPERIOD_MODE == merge If a service is started on 2008-01-09, the end of the billing period will be 2008-02-29, which is 1 month + 21 day. We can calculate with: ( standard_cost / normal_month_num_days ) * num_days_in_partial_month == extra_amount total_amount = (extra_amount + normal_amount) Note: we do not increase included units for licenses service types, since these need to remain fixed and do not scale with the month. */ // check if the period start date is not the first of the month - this means that the period // is an inital partial period. if (time_calculate_daynum($period_data["date_start"]) != "01") { // very first billing month log_write("debug", "services_invoicegen", "First billing month for this service, adjusting pricing to suit days."); // figure out normal period length - rememeber, whilst service may be configured to align monthly, it needs to // handle pricing calculations for variations such as month, year, etc. // get the number of days of a regular period - this correctly handles, month, year, etc // 1. generate the next period dates, this will be of regular length // 2. calculate number of days $tmp_dates = service_period_dates_generate($period_data["date_period_next"], $obj_service->data["billing_cycle_string"], $obj_service->data["billing_mode_string"]); $regular_period_num_days = sql_get_singlevalue("SELECT DATEDIFF('" . $tmp_dates["end"] . "', '" . $tmp_dates["start"] . "') as value"); // process for short/long periods // if ($GLOBALS["config"]["SERVICE_PARTPERIOD_MODE"] == "seporate") { log_write("debug", "services_invoicegen", "Adjusting for partial month period (SERVICE_PARTPERIOD_MODE == seporate)"); // work out the total number of days in partial month $short_month_days_total = time_calculate_daynum(time_calculate_monthdate_last($period_data["date_start"])); $short_month_days_short = $short_month_days_total - time_calculate_daynum($period_data["date_start"]); log_write("debug", "services_invoicegen", "Short initial billing period of {$short_month_days_short} days"); // calculate correct base fee $obj_service->data["price"] = $obj_service->data["price"] / $regular_period_num_days * $short_month_days_short; // calculate ratio $ratio = $short_month_days_short / $regular_period_num_days; log_write("debug", "services_invoicegen", "Calculated service bill ratio of {$ratio} to handle short period."); } else { log_write("debug", "services_invoicegen", "Adjusting for extended month period (SERVICE_PARTPERIOD_MODE == merge"); // work out the number of days extra $extra_month_days_total = time_calculate_daynum(time_calculate_monthdate_last($period_data["date_start"])); $extra_month_days_extra = $extra_month_days_total - time_calculate_daynum($period_data["date_start"]); log_debug("services_invoicegen", "{$extra_month_days_extra} additional days ontop of started billing period"); // calculate correct base fee $obj_service->data["price"] = $obj_service->data["price"] / $regular_period_num_days * $extra_month_days_extra + $obj_service->data["price"]; // calculate ratio $ratio = ($extra_month_days_extra + $extra_month_days_total) / $regular_period_num_days; log_write("debug", "services_invoicegen", "Calculated service bill ratio of {$ratio} to handle extended period."); } } } /* Service Last Period Handling If this is the last period for a service, it may be of a different link to the regular full period term - this effects both month and period based services. */ if ($period_data["date_period_last"] != "0000-00-00") { log_write("debug", "services_invoicegen", "Service has a final period date set (" . $period_data["date_period_last"] . ")"); if ($period_data["date_period_last"] == $period_data["date_end"] || time_date_to_timestamp($period_data["date_period_last"]) < time_date_to_timestamp($period_data["date_end"])) { log_write("debug", "services_invoicegen", "Service is a final period, checking for time adjustment (if any)"); // fetch the regular end date $orig_dates = service_period_dates_generate($period_data["date_start"], $obj_service->data["billing_cycle_string"], $obj_service->data["billing_mode_string"]); if ($orig_dates["end"] != $period_data["date_end"]) { // work out the total number of days $time = NULL; $time["start"] = time_date_to_timestamp($period_data["date_start"]); $time["end_orig"] = time_date_to_timestamp($orig_dates["end"]); $time["end_new"] = time_date_to_timestamp($period_data["date_end"]); $time["orig_days"] = sprintf("%d", ($time["end_orig"] - $time["start"]) / 86400); $time["new_days"] = sprintf("%d", ($time["end_new"] - $time["start"]) / 86400); log_write("debug", "services_invoicegen", "Short initial billing period of " . $time["new_days"] . " days rather than expected " . $time["orig_days"] . ""); // calculate correct base fee $obj_service->data["price"] = $obj_service->data["price"] / $time["orig_days"] * $time["new_days"]; // calculate ratio $ratio = $time["new_days"] / $time["orig_days"]; log_write("debug", "services_invoicegen", "Calculated service bill ratio of {$ratio} to handle short period."); unset($time); } else { log_write("debug", "services_invoicegen", "Final service period is regular size, no adjustment required."); } } } /* Service Base Plan Item Note: There can be a suitation where we shouldn't charge for the base plan fee, when the period end date is older than the service first start date. This can happen due to the migration mode, which creates a usage-only period before the actual plan starts properly. */ if (time_date_to_timestamp($period_data["date_period_first"]) < time_date_to_timestamp($period_data["date_end"]) && $period_data["mode"] == "standard") { // date of the period is newer than the start date log_write("debug", "inc_service_invoicegen", "Generating base plan item for period " . $period_data["date_start"] . " to " . $period_data["date_end"] . ""); // start the item $invoice_item = new invoice_items(); $invoice_item->id_invoice = $invoiceid; $invoice_item->type_invoice = "ar"; $invoice_item->type_item = "service"; $itemdata = array(); // chart ID $itemdata["chartid"] = $obj_service->data["chartid"]; // service ID $itemdata["customid"] = $obj_service->id; // no units apply $itemdata["units"] = ""; // description switch ($obj_service->data["typeid_string"]) { case "phone_single": $itemdata["description"] = addslashes($obj_service->data["name_service"]) . " from " . $period_data["date_start"] . " to " . $period_data["date_end"] . " (" . $obj_service->data["phone_ddi_single"] . ")"; if ($obj_service->data["description"]) { $itemdata["description"] .= "\n\n"; $itemdata["description"] .= addslashes($obj_service->data["description"]); } break; default: $itemdata["description"] = addslashes($obj_service->data["name_service"]) . " from " . $period_data["date_start"] . " to " . $period_data["date_end"]; if ($obj_service->data["description"]) { $itemdata["description"] .= "\n\n"; $itemdata["description"] .= addslashes($obj_service->data["description"]); } break; } // handle final periods if ($period_data["date_period_last"] != "0000-00-00" && $period_data["date_start"] == $period_data["date_end"]) { $itemdata["description"] = addslashes($obj_service->data["name_service"]) . " service terminated as of " . $period_data["date_end"] . ""; } // is this service item part of a bundle? if it is, we shouldn't charge for the plan if (!empty($obj_service->data["id_bundle_component"])) { // no charge for the base plan, since this is handled by // the bundle item itself $itemdata["price"] = "0.00"; $itemdata["quantity"] = 1; $itemdata["discount"] = "0"; // append description $itemdata["description"] .= " (part of bundle)"; } else { // amount $itemdata["price"] = $obj_service->data["price"]; $itemdata["quantity"] = 1; $itemdata["discount"] = $obj_service->data["discount"]; } // create item $invoice_item->prepare_data($itemdata); $invoice_item->action_update(); unset($invoice_item); } else { log_write("debug", "inc_service_invoicegen", "Skipping base plan item generation, due to migration or rebill mode operation"); } /* Service Usage Items Create another item on the invoice for any usage, provided that the service type is a usage service */ $period_usage_data = array(); /* Depending on the service type, we need to handle the usage period in one of two ways: 1. Invoice usage for the current period that has just ended (periodend/monthend) 2. Invoice usage for the period BEFORE the current period. (periodtelco/monthtelco) */ if ($obj_service->data["billing_mode_string"] == "periodtelco" || $obj_service->data["billing_mode_string"] == "monthtelco") { log_write("debug", "service_invoicegen", "Determining previous period for telco-style usage billing (if any)"); if ($period_data["mode"] == "standard") { // fetch previous period (if any) - we can determine the previous by subtracting one day from the current period start date. $tmp_date = explode("-", $period_data["date_start"]); $period_usage_data["date_end"] = date("Y-m-d", mktime(0, 0, 0, $tmp_date[1], $tmp_date[2] - 1, $tmp_date[0])); } elseif ($period_data["mode"] == "rebill_usage") { // use the period as the usage period log_write("debug", "service_invoicegen", "Using the selected period " . $period_usage_data["date_end"] . " for rebill_usage mode"); $period_usage_data["date_end"] = $period_data["date_end"]; } // fetch period data to confirm previous period $sql_obj = new sql_query(); $sql_obj->string = "SELECT id, date_start, date_end FROM services_customers_periods WHERE id_service_customer='" . $period_data["id_service_customer"] . "' AND date_end='" . $period_usage_data["date_end"] . "' LIMIT 1"; $sql_obj->execute(); if ($sql_obj->num_rows()) { log_write("debug", "service_invoicegen", "Billing for seporate past usage period"); // there is a valid usage period $period_usage_data["active"] = "yes"; // fetch dates $sql_obj->fetch_array(); $period_usage_data["id_service_customer"] = $period_data["id_service_customer"]; $period_usage_data["id"] = $sql_obj->data[0]["id"]; $period_usage_data["date_start"] = $sql_obj->data[0]["date_start"]; $period_usage_data["date_end"] = $sql_obj->data[0]["date_end"]; // tracing log_write("debug", "service_invoicegen", "Current period is (" . $period_data["date_start"] . " to " . $period_data["date_end"] . "), usage period is (" . $period_usage_data["date_start"] . " to " . $period_usage_data["date_end"] . ")"); // reset ratio $ratio = 1; /* TODO: This code is a replicant of the section further above for calculating partial periods and should really be functionalised as part of service usage continual improvements */ // calculate usage abnormal period if ($obj_service->data["billing_mode_string"] == "monthend" || $obj_service->data["billing_mode_string"] == "monthadvance" || $obj_service->data["billing_mode_string"] == "monthtelco") { log_debug("services_invoicegen", "Usage period service bills by month date"); if (time_calculate_daynum($period_usage_data["date_start"]) != "01") { // very first billing month log_write("debug", "services_invoicegen", "First billing month for this usage period, adjusting pricing to suit days."); if ($GLOBALS["config"]["SERVICE_PARTPERIOD_MODE"] == "seporate") { log_write("debug", "services_invoicegen", "Adjusting for partial month period (SERVICE_PARTPERIOD_MODE == seporate)"); // work out the total number of days $short_month_days_total = time_calculate_daynum(time_calculate_monthdate_last($period_usage_data["date_start"])); $short_month_days_short = $short_month_days_total - time_calculate_daynum($period_usage_data["date_start"]); log_write("debug", "services_invoicegen", "Short initial billing period of {$short_month_days_short} days"); // calculate ratio $ratio = $short_month_days_short / $short_month_days_total; log_write("debug", "services_invoicegen", "Calculated service bill ratio of {$ratio} to handle short period."); } else { log_write("debug", "services_invoicegen", "Adjusting for extended month period (SERVICE_PARTPERIOD_MODE == merge"); // work out the number of days extra $extra_month_days_total = time_calculate_daynum(time_calculate_monthdate_last($period_usage_data["date_start"])); $extra_month_days_extra = $extra_month_days_total - time_calculate_daynum($period_usage_data["date_start"]); log_debug("services_invoicegen", "{$extra_month_days_extra} additional days ontop of started billing period"); // calculate ratio $ratio = ($extra_month_days_extra + $extra_month_days_total) / $extra_month_days_total; log_write("debug", "services_invoicegen", "Calculated service bill ratio of {$ratio} to handle extended period."); } } } // end of calculate usage abnormal period if ($period_data["date_period_last"] != "0000-00-00") { log_write("debug", "services_invoicegen", "Service has a final period date set (" . $period_data["date_period_last"] . ")"); if ($period_data["date_period_last"] == $period_usage_data["date_end"] || time_date_to_timestamp($period_data["date_period_last"]) < time_date_to_timestamp($period_usage_data["date_end"])) { log_write("debug", "services_invoicegen", "Service is a final period, checking for time adjustment (if any)"); // fetch the regular end date $orig_dates = service_period_dates_generate($period_usage_data["date_start"], $obj_service->data["billing_cycle_string"], $obj_service->data["billing_mode_string"]); if ($orig_dates["end"] != $period_usage_data["date_end"]) { // work out the total number of days $time = NULL; $time["start"] = time_date_to_timestamp($period_usage_data["date_start"]); $time["end_orig"] = time_date_to_timestamp($orig_dates["end"]); $time["end_new"] = time_date_to_timestamp($period_usage_data["date_end"]); $time["orig_days"] = sprintf("%d", ($time["end_orig"] - $time["start"]) / 86400); $time["new_days"] = sprintf("%d", ($time["end_new"] - $time["start"]) / 86400); log_write("debug", "services_invoicegen", "Short initial billing period of " . $time["new_days"] . " days rather than expected " . $time["orig_days"] . ""); // calculate correct base fee $obj_service->data["price"] = $obj_service->data["price"] / $time["orig_days"] * $time["new_days"]; // calculate ratio $ratio = $time["new_days"] / $time["orig_days"]; log_write("debug", "services_invoicegen", "Calculated service bill ratio of {$ratio} to handle short period."); unset($time); } else { log_write("debug", "services_invoicegen", "Final service period is regular size, no adjustment required."); } } } } else { log_write("debug", "service_invoicegen", "Not billing for past usage, as this appears to be the first plan period so no usage can exist yet"); } } else { log_write("debug", "service_invoicegen", "Using plan period as data usage period (" . $period_data["date_start"] . " to " . $period_data["date_end"] . ""); // use current period $period_usage_data["active"] = "yes"; $period_usage_data["id_service_customer"] = $period_data["id_service_customer"]; $period_usage_data["id"] = $period_data["id"]; $period_usage_data["date_start"] = $period_data["date_start"]; $period_usage_data["date_end"] = $period_data["date_end"]; } /* Create usage items if there is a valid usage period */ if (!empty($period_usage_data["active"])) { log_write("debug", "service_invoicegen", "Creating usage items due to active usage period"); switch ($obj_service->data["typeid_string"]) { case "generic_with_usage": /* GENERIC_WITH_USAGE This service is to be used for any non-traffic, non-time accounting service that needs to track usage. Examples of this could be counting the number of API requests, size of disk usage on a vhost, etc. */ log_write("debug", "service_invoicegen", "Processing usage items for generic_with_usage"); /* Usage Item Basics */ // start the item $invoice_item = new invoice_items(); $invoice_item->id_invoice = $invoiceid; $invoice_item->type_invoice = "ar"; $invoice_item->type_item = "service_usage"; $itemdata = array(); $itemdata["chartid"] = $obj_service->data["chartid"]; $itemdata["description"] = addslashes($obj_service->data["name_service"]) . " usage from " . $period_usage_data["date_start"] . " to " . $period_usage_data["date_end"]; $itemdata["customid"] = $obj_service->id; $itemdata["discount"] = 0; /* Adjust Included Units to handle partial or extended periods */ if ($ratio != "1") { $obj_service->data["included_units"] = sprintf("%d", $obj_service->data["included_units"] * $ratio); } /* Fetch usage amount */ $usage_obj = new service_usage_generic(); $usage_obj->id_service_customer = $period_usage_data["id_service_customer"]; $usage_obj->date_start = $period_usage_data["date_start"]; $usage_obj->date_end = $period_usage_data["date_end"]; if ($usage_obj->load_data_service()) { $usage_obj->fetch_usagedata(); if ($usage_obj->data["total_byunits"]) { $usage = $usage_obj->data["total_byunits"]; } else { $usage = $usage_obj->data["total"]; } } unset($usage_obj); /* Charge for the usage in units */ $unitname = addslashes($obj_service->data["units"]); if ($usage > $obj_service->data["included_units"]) { // there is excess usage that we can bill for. $usage_excess = $usage - $obj_service->data["included_units"]; // set item attributes $itemdata["price"] = $obj_service->data["price_extraunits"]; $itemdata["quantity"] = $usage_excess; $itemdata["units"] = $unitname; // description example: Used 120 ZZ out of 50 ZZ included in plan // Excess usage of 70 ZZ charged at $5.00 per ZZ $itemdata["description"] .= "\nUsed {$usage} {$unitname} out of " . $obj_service->data["included_units"] . " {$unitname} included in plan."; $itemdata["description"] .= "\nExcess usage of {$usage_excess} {$unitname} charged at " . $obj_service->data["price_extraunits"] . " per {$unitname}."; } else { // description example: Used 120 ZZ out of 50 ZZ included in plan $itemdata["description"] .= "\nUsed {$usage} {$unitname} out of " . $obj_service->data["included_units"] . " {$unitname} included in plan."; } /* Add the item to the invoice */ $invoice_item->prepare_data($itemdata); $invoice_item->action_update(); unset($invoice_item); /* Update usage value for period - this summary value is visable on the service history page and saves having to query lots of records to generate period totals. */ $sql_obj = new sql_query(); $sql_obj->string = "UPDATE services_customers_periods SET usage_summary='{$usage}' WHERE id='" . $period_usage_data["id"] . "' LIMIT 1"; $sql_obj->execute(); break; case "licenses": /* LICENSES No data usage, but there is a quantity field for the customer's account to specify the quantity of licenses that they have. */ log_write("debug", "service_invoicegen", "Processing usage items for licenses"); /* Usage Item Basics */ // start the item $invoice_item = new invoice_items(); $invoice_item->id_invoice = $invoiceid; $invoice_item->type_invoice = "ar"; $invoice_item->type_item = "service_usage"; $itemdata = array(); $itemdata["chartid"] = $obj_service->data["chartid"]; $itemdata["description"] = addslashes($obj_service->data["name_service"]) . " usage from " . $period_usage_data["date_start"] . " to " . $period_usage_data["date_end"]; $itemdata["customid"] = $obj_service->id; $itemdata["discount"] = 0; /* Determine Additional Charges */ // charge for any extra licenses if ($obj_service->data["quantity"] > $obj_service->data["included_units"]) { // there is excess usage that we can bill for. $licenses_excess = $obj_service->data["quantity"] - $obj_service->data["included_units"]; // set item attributes $itemdata["price"] = $obj_service->data["price_extraunits"] * $ratio; $itemdata["quantity"] = $licenses_excess; $itemdata["units"] = addslashes($obj_service->data["units"]); // description example: 10 licences included // 2 additional licenses charged at $24.00 each $itemdata["description"] .= "\n" . $obj_service->data["included_units"] . " " . $obj_service->data["units"] . " included"; $itemdata["description"] .= "\n{$licenses_excess} additional " . $obj_service->data["units"] . " charged at " . $obj_service->data["price_extraunits"] . " each."; } else { // description example: 10 licenses $itemdata["description"] .= "\n" . $period_data["quantity"] . " " . $period_data["units"] . "."; } /* Add the item to the invoice */ $invoice_item->prepare_data($itemdata); $invoice_item->action_update(); unset($invoice_item); break; case "time": /* TIME Simular to the generic usage type, but instead of units being a text field, units is an ID to the service_units table. */ log_write("debug", "service_invoicegen", "Processing usage items for time traffic"); /* Usage Item Basics */ // start the item $invoice_item = new invoice_items(); $invoice_item->id_invoice = $invoiceid; $invoice_item->type_invoice = "ar"; $invoice_item->type_item = "service_usage"; $itemdata = array(); $itemdata["chartid"] = $obj_service->data["chartid"]; $itemdata["description"] = addslashes($obj_service->data["name_service"]) . " usage from " . $period_usage_data["date_start"] . " to " . $period_usage_data["date_end"]; $itemdata["customid"] = $obj_service->id; $itemdata["discount"] = 0; /* Adjust Included Units to handle partial or extended periods */ if ($ratio != "1") { $obj_service->data["included_units"] = sprintf("%d", $obj_service->data["included_units"] * $ratio); } /* Fetch usage amount */ $usage_obj = new service_usage_generic(); $usage_obj->id_service_customer = $period_usage_data["id_service_customer"]; $usage_obj->date_start = $period_usage_data["date_start"]; $usage_obj->date_end = $period_usage_data["date_end"]; if ($usage_obj->load_data_service()) { $usage_obj->fetch_usagedata(); if ($usage_obj->data["total_byunits"]) { $usage = $usage_obj->data["total_byunits"]; } else { $usage = $usage_obj->data["total"]; } } unset($usage_obj); /* Charge for the usage in units */ $unitname = sql_get_singlevalue("SELECT name as value FROM service_units WHERE id='" . $obj_service->data["units"] . "'"); if ($usage > $obj_service->data["included_units"]) { // there is excess usage that we can bill for. $usage_excess = $usage - $obj_service->data["included_units"]; // set item attributes $itemdata["price"] = $obj_service->data["price_extraunits"]; $itemdata["quantity"] = $usage_excess; $itemdata["units"] = $unitname; // description example: Used 120 GB out of 50 GB included in plan // Excess usage of 70 GB charged at $5.00 per GB $itemdata["description"] .= "\nUsed {$usage} {$unitname} out of " . $obj_service->data["included_units"] . " {$unitname} included in plan."; $itemdata["description"] .= "\nExcess usage of {$usage_excess} {$unitname} charged at " . $obj_service->data["price_extraunits"] . " per {$unitname}."; } else { // description example: Used 10 out of 50 included units $itemdata["description"] .= "\nUsed {$usage} {$unitname} out of " . $obj_service->data["included_units"] . " {$unitname} included in plan."; } /* Add the item to the invoice */ $invoice_item->prepare_data($itemdata); $invoice_item->action_update(); unset($invoice_item); /* Update usage value for period - this summary value is visable on the service history page and saves having to query lots of records to generate period totals. */ $sql_obj = new sql_query(); $sql_obj->string = "UPDATE services_customers_periods SET usage_summary='{$usage}' WHERE id='" . $period_usage_data["id"] . "' LIMIT 1"; $sql_obj->execute(); break; case "data_traffic": /* DATA_TRAFFIC We make use of the service_usage_traffic logic to determine the usage across all IP addressess assigned to this customer and then bill accordingly. */ log_write("debug", "service_invoicegen", "Processing usage items for time/data_traffic"); /* Fetch data traffic plan usage type */ $unitname = sql_get_singlevalue("SELECT name as value FROM service_units WHERE id='" . $obj_service->data["units"] . "'"); /* Fetch usage amount - the returned usage structure will include breakdown of traffic by configured types. */ $usage_obj = new service_usage_traffic(); $usage_obj->id_service_customer = $period_usage_data["id_service_customer"]; $usage_obj->date_start = $period_usage_data["date_start"]; $usage_obj->date_end = $period_usage_data["date_end"]; /* Fetch Traffic Caps & Details Returns all the traffic cap types including overrides. id_type, id_cap, type_name, type_label, cap_mode, cap_units_included, cap_units_price */ $traffic_types_obj = new traffic_caps(); $traffic_types_obj->id_service = $obj_service->id; $traffic_types_obj->id_service_customer = $period_usage_data["id_service_customer"]; $traffic_types_obj->load_data_traffic_caps(); $traffic_types_obj->load_data_override_caps(); /* Generate Traffic Bills */ if ($usage_obj->load_data_service()) { $usage_obj->fetch_usage_traffic(); foreach ($traffic_types_obj->data as $data_traffic_cap) { // Adjust Included Units to handle partial or extended periods if ($ratio != "1") { $data_traffic_cap["cap_units_included"] = sprintf("%d", $data_traffic_cap["cap_units_included"] * $ratio); } // if there is only a single traffic cap, we should make the traffic type name blank, since there's only going to be // one line item anyway. if ($traffic_types_obj->data_num_rows == 1) { $data_traffic_cap["type_name"] = ""; } // if the any traffic type is zero and there are other traffic types, we should skip it, since most likely // the other traffic types provide everything expected. // if ($traffic_types_obj->data_num_rows > 1 && $data_traffic_cap["type_label"] == "*" && $usage_obj->data["total_byunits"]["*"] == 0) { continue; } // start service item $invoice_item = new invoice_items(); $invoice_item->id_invoice = $invoiceid; $invoice_item->type_invoice = "ar"; $invoice_item->type_item = "service_usage"; $itemdata = array(); $itemdata["chartid"] = $obj_service->data["chartid"]; $itemdata["customid"] = $obj_service->id; // base details $itemdata["price"] = 0; $itemdata["quantity"] = 0; $itemdata["discount"] = 0; $itemdata["units"] = ""; $itemdata["description"] = addslashes($obj_service->data["name_service"]) . " usage from " . $period_usage_data["date_start"] . " to " . $period_usage_data["date_end"]; if ($data_traffic_cap["cap_mode"] == "unlimited") { // unlimited data cap, there will never be any excess traffic charges, so the line item // description should be purely for informative purposes. $itemdata["description"] .= "\nUnlimited " . addslashes($data_traffic_cap["type_name"]) . " traffic, total of " . $usage_obj->data["total_byunits"][$data_traffic_cap["type_label"]] . " {$unitname} used\n"; } else { // capped traffic - check for excess changes, otherwise just report on how much traffic // that the customer used. // description example: Used 10 GB out of 50 GB included in plan $itemdata["description"] .= "\nCapped " . addslashes($data_traffic_cap["type_name"]) . " traffic, used " . $usage_obj->data["total_byunits"][$data_traffic_cap["type_label"]] . " {$unitname} out of " . $data_traffic_cap["cap_units_included"] . " {$unitname} in plan."; // handle excess charges // if ($usage_obj->data["total_byunits"][$data_traffic_cap["type_label"]] > $data_traffic_cap["cap_units_included"]) { // there is excess usage that we can bill for. $usage_excess = $usage_obj->data["total_byunits"][$data_traffic_cap["type_label"]] - $data_traffic_cap["cap_units_included"]; // set item attributes $itemdata["price"] = $data_traffic_cap["cap_units_price"]; $itemdata["quantity"] = $usage_excess; $itemdata["units"] = $unitname; // description example: Excess usage of 70 GB charged at $5.00 per GB $itemdata["description"] .= "\nExcess usage of {$usage_excess} {$unitname} charged at " . format_money($data_traffic_cap["cap_units_price"]) . " per {$unitname}."; } } // end of traffic cap mode // add trunk usage item // $invoice_item->prepare_data($itemdata); $invoice_item->action_update(); unset($invoice_item); } } /* Update usage value for period - this summary value is visable on the service history page and saves having to query lots of records to generate period totals. */ $sql_obj = new sql_query(); $sql_obj->string = "UPDATE services_customers_periods SET usage_summary='" . $usage_obj->data["total_byunits"]["total"] . "' WHERE id='" . $period_usage_data["id"] . "' LIMIT 1"; $sql_obj->execute(); unset($usage_obj); unset($traffic_types_obj); break; case "phone_single": case "phone_tollfree": case "phone_trunk": /* PHONE_* SERVICES The phone services are special and contain multiple usage items, for: * Additional DDI numbers * Additional Trunks There are also multiple items for the call charges, grouped into one item for each DDI. */ log_write("debug", "service_invoicegen", "Processing usage items for phone_single/phone_tollfree/phone_trunk"); // setup usage object $usage_obj = new service_usage_cdr(); $usage_obj->id_service_customer = $period_usage_data["id_service_customer"]; $usage_obj->date_start = $period_usage_data["date_start"]; $usage_obj->date_end = $period_usage_data["date_end"]; $usage_obj->load_data_service(); /* 1. DDI CHARGES We need to fetch the total number of DDIs and see if there are any excess charges due to overage of the allocated amount in the plan. */ if ($obj_service->data["typeid_string"] == "phone_trunk" && $period_data["mode"] == "standard") { // start service item $invoice_item = new invoice_items(); $invoice_item->id_invoice = $invoiceid; $invoice_item->type_invoice = "ar"; $invoice_item->type_item = "service_usage"; $itemdata = array(); $itemdata["chartid"] = $obj_service->data["chartid"]; $itemdata["customid"] = $obj_service->id; $itemdata["discount"] = 0; // fetch DDI usage $usage = $usage_obj->load_data_ddi(); // determine excess usage charges if ($usage > $obj_service->data["phone_ddi_included_units"]) { // there is excess usage that we can bill for. $usage_excess = $usage - $obj_service->data["phone_ddi_included_units"]; // set item attributes $itemdata["price"] = $obj_service->data["phone_ddi_price_extra_units"] * $ratio; log_write("debug", "DEBUG", "Ratio is {$ratio}, price is " . $itemdata["price"] . ""); $itemdata["quantity"] = $usage_excess; $itemdata["units"] = "DDIs"; if ($obj_service->data["phone_ddi_included_units"]) { $itemdata["description"] = $obj_service->data["phone_ddi_included_units"] . "x DDI numbers included in service plan plus additional " . $usage_excess . "x numbers from " . $period_usage_data["date_start"] . " to " . $period_usage_data["date_end"] . ""; } else { // no included units, we use an alternative string format $itemdata["description"] = $usage_excess . "x DDI numbers from " . $period_usage_data["date_start"] . " to " . $period_usage_data["date_end"] . ""; } } else { // no charge for this item $itemdata["description"] = $obj_service->data["phone_ddi_included_units"] . "x DDI numbers included in service plan from " . $period_usage_data["date_start"] . " to " . $period_usage_data["date_end"] . ""; } // add trunk usage item $invoice_item->prepare_data($itemdata); $invoice_item->action_update(); unset($invoice_item); } /* 2. Trunk Charges */ if (($obj_service->data["typeid_string"] == "phone_trunk" || $obj_service->data["typeid_string"] == "phone_tollfree") && $period_data["mode"] == "standard") { // fetch the number of trunks included in the plan, along with the number provided // we can then see if there are any excess charges for these // start service item $invoice_item = new invoice_items(); $invoice_item->id_invoice = $invoiceid; $invoice_item->type_invoice = "ar"; $invoice_item->type_item = "service_usage"; $itemdata = array(); $itemdata["chartid"] = $obj_service->data["chartid"]; $itemdata["customid"] = $obj_service->id; $itemdata["discount"] = 0; // determine excess usage charges if ($obj_service->data["phone_trunk_quantity"] > $obj_service->data["phone_trunk_included_units"]) { // there is excess usage that we can bill for. $usage_excess = $obj_service->data["phone_trunk_quantity"] - $obj_service->data["phone_trunk_included_units"]; // set item attributes $itemdata["price"] = $obj_service->data["phone_trunk_price_extra_units"] * $ratio; $itemdata["quantity"] = $usage_excess; $itemdata["units"] = "trunks"; if ($obj_service->data["phone_trunk_included_units"]) { $itemdata["description"] = $obj_service->data["phone_trunk_included_units"] . "x trunks included in service plan plus additional " . $usage_excess . "x trunks from " . $period_usage_data["date_start"] . " to " . $period_usage_data["date_end"] . ""; } else { // no included trunks, adjust string to suit. $itemdata["description"] = $usage_excess . "x trunks from " . $period_usage_data["date_start"] . " to " . $period_usage_data["date_end"] . ""; } } else { // no charge for this item $itemdata["description"] = $obj_service->data["phone_trunk_included_units"] . "x trunks included in service plan from " . $period_usage_data["date_start"] . " to " . $period_usage_data["date_end"] . ""; } // add trunk usage item $invoice_item->prepare_data($itemdata); $invoice_item->action_update(); unset($invoice_item); } /* Call Charges Use CDR usage billing module to handle call charges. */ $usage_obj = new service_usage_cdr(); $usage_obj->id_service_customer = $period_usage_data["id_service_customer"]; $usage_obj->date_start = $period_usage_data["date_start"]; $usage_obj->date_end = $period_usage_data["date_end"]; $billgroup_obj = new sql_query(); $billgroup_obj->string = "SELECT id, billgroup_name FROM cdr_rate_billgroups"; $billgroup_obj->execute(); $billgroup_obj->fetch_array(); if ($usage_obj->load_data_service()) { $usage_obj->fetch_usage_calls(); foreach ($billgroup_obj->data as $data_billgroup) { foreach ($usage_obj->data_ddi as $ddi) { if ($usage_obj->data[$ddi][$data_billgroup["id"]]["charges"] > 0) { // start service item $invoice_item = new invoice_items(); $invoice_item->id_invoice = $invoiceid; $invoice_item->type_invoice = "ar"; $invoice_item->type_item = "service_usage"; $itemdata = array(); $itemdata["chartid"] = $obj_service->data["chartid"]; $itemdata["customid"] = $obj_service->id; // extra service details $itemdata["id_service_customer"] = $period_usage_data["id_service_customer"]; $itemdata["id_period"] = $period_usage_data["id"]; // determine excess usage charges $itemdata["discount"] = 0; $itemdata["price"] = $usage_obj->data[$ddi][$data_billgroup["id"]]["charges"]; $itemdata["quantity"] = "1"; $itemdata["units"] = ""; $itemdata["description"] = $data_billgroup["billgroup_name"] . " call charges for {$ddi} from " . $period_usage_data["date_start"] . " to " . $period_usage_data["date_end"] . ""; $itemdata["cdr_billgroup"] = $data_billgroup["id"]; // add trunk usage item $invoice_item->prepare_data($itemdata); $invoice_item->action_update(); unset($invoice_item); } else { log_write("debug", "inc_service_invoicegen", "Excluding DDI {$ddi} from " . $data_billgroup["billgroup_name"] . " due to no charges for the current period"); } } } } unset($usage_obj); /* If enabled, generate the CDR output format for the current service usage and attach it to the invoice via the invoice journal. This feature can be enabled/disabled on a per-customer per-service basis. */ if ($obj_service->data["billing_cdr_csv_output"]) { log_write("debug", "inc_service_invoicegen", "Generating CDR export file and attaching to invoice journal"); // generate the CSV formatted export. $cdr_options = array('id_customer' => $customer_data["id"], 'id_service_customer' => $period_usage_data["id_service_customer"], 'period_start' => $period_usage_data["date_start"], 'period_end' => $period_usage_data["date_end"]); $csv = new cdr_csv($cdr_options); if (!($cdr_output = $csv->getCSV())) { log_write("error", "inc_service_invoicegen", "Unable to generate CSV ouput for the configured range"); return 0; } // create journal entry $journal = new journal_process(); $journal->prepare_set_journalname("account_ar"); $journal->prepare_set_customid($invoiceid); $journal->prepare_set_type("file"); // we use the prefix "SERVICE:" to find the journal at invoice time $journal->prepare_set_title("SERVICE: Service CDR Export Attachment"); // details can be anything (just a text block) $data["content"] = NULL; $data["content"] .= "Automatically exported CDR for service " . addslashes($obj_service->data["name_service"]) . "\n"; $data["content"] .= "\n"; $journal->prepare_set_content($data["content"]); $journal->action_update(); // create journal entry $journal->action_lock(); // lock entry to avoid users deleting it or breaking it // upload file as an attachment for the journal $file_obj = new file_storage(); $file_obj->data["type"] = "journal"; $file_obj->data["customid"] = $journal->structure["id"]; $file_obj->data["file_name"] = "invoice_" . $invoicecode . "_service_CDR_export.csv"; if (!$file_obj->action_update_var($cdr_output)) { log_write("error", "inc_service_invoicegen", "Unable to upload export CDR invoice to journal."); } unset($csv); unset($journal); unset($file_obj); unset($cdr_output); } break; case "generic_no_usage": case "bundle": // nothing todo for these service types log_write("debug", "service_invoicegen", "Not processing usage, this is a non-usage service type"); break; default: // we should always match all service types, even if we don't need to do anything // in particular for that type. die("Unable to process unknown service type: " . $obj_service->data["typeid_string"] . ""); break; } // end of processing usage } else { log_write("debug", "service_invoicegen", "Not billing for current usage, as this appears to be the first plan period so no usage can exist yet"); } /* Set invoice ID for period - this prevents the period from being added to any other invoices and allows users to see which invoice it was billed under */ // set for plan period $sql_obj = new sql_query(); $sql_obj->string = "UPDATE services_customers_periods SET invoiceid='{$invoiceid}', rebill='0' WHERE id='" . $period_data["id"] . "' LIMIT 1"; $sql_obj->execute(); // set for usage period if (!empty($period_usage_data["active"])) { $sql_obj = new sql_query(); $sql_obj->string = "UPDATE services_customers_periods SET invoiceid_usage='{$invoiceid}' WHERE id='" . $period_usage_data["id"] . "' LIMIT 1"; $sql_obj->execute(); } } // end of processing periods /* Only process orders and invoice summary details if we had no errors above. */ if (!error_check()) { /* Process any customer orders */ if ($GLOBALS["config"]["ORDERS_BILL_ONSERVICE"]) { log_write("debug", "inc_service_invoicegen", "Checking for customer orders to add to service invoice"); $obj_customer_orders = new customer_orders(); $obj_customer_orders->id = $customer_data["id"]; if ($obj_customer_orders->check_orders_num()) { log_write("debug", "inc_service_invoicegen", "Order items exist, adding them to service invoice"); $obj_customer_orders->invoice_generate($invoiceid); } } else { log_write("debug", "inc_service_invoicegen", "Not checking for customer orders, ORDERS_BILL_ONSERVICE is disabled currently"); } /* Update the invoice details + Ledger Processes: - taxes - ledger - invoice summary We use the invoice_items class to perform these tasks, but we don't need to define an item ID for the functions being used to work. */ $invoice = new invoice_items(); $invoice->id_invoice = $invoiceid; $invoice->type_invoice = "ar"; $invoice->action_update_tax(); $invoice->action_update_ledger(); $invoice->action_update_total(); unset($invoice); /* Update period information with invoiceid */ $sql_obj = new sql_query(); $sql_obj->string = "UPDATE services_customers_periods SET invoiceid='{$invoiceid}' WHERE id='" . $period_data["id"] . "'"; $sql_obj->execute(); } // end if error check /* Automatic Payments Makes automatic invoice payments using sources such as customer credit pools, reoccuring credit card transactions and other sources. */ if ($GLOBALS["config"]["ACCOUNTS_AUTOPAY"]) { log_write("debug", "inc_services_invoicegen", "Autopay Functionality Enabled, running appropiate functions for invoice ID {$invoiceid}"); $obj_autopay = new invoice_autopay(); $obj_autopay->id_invoice = $invoiceid; $obj_autopay->type_invoice = "ar"; $obj_autopay->autopay(); unset($obj_autopay); } /* Commit Conduct final error check, before commiting the new invoice and sending the customer an email if appropiate. (we obviously don't want to email them if the invoice is getting rolled back!) */ $sql_obj = new sql_query(); if (error_check()) { $sql_obj->trans_rollback(); log_write("error", "inc_services_invoicegen", "An error occured whilst creating service invoice. No changes have been made."); } else { $sql_obj->trans_commit(); // invoice creation complete - remove any notifications made by the invoice functions and return // our own notification $_SESSION["notification"]["message"] = array(); log_write("notification", "inc_services_invoicegen", "New invoice {$invoicecode} for customer " . $customer_data["code_customer"] . " created"); /* Send the invoice to the customer as a PDF via email */ $emailed = "unsent"; if (sql_get_singlevalue("SELECT value FROM config WHERE name='EMAIL_ENABLE'") == "enabled") { if (sql_get_singlevalue("SELECT value FROM config WHERE name='ACCOUNTS_INVOICE_AUTOEMAIL'") == "enabled") { // load completed invoice data $invoice = new invoice(); $invoice->id = $invoiceid; $invoice->type = "ar"; $invoice->load_data(); $invoice->load_data_export(); if ($invoice->data["amount_total"] > 0) { // generate an email $email = $invoice->generate_email(); // send email $invoice->email_invoice("system", $email["to"], $email["cc"], $email["bcc"], $email["subject"], $email["message"]); // complete log_write("notification", "inc_services_invoicegen", "Invoice {$invoicecode} has been emailed to customer (" . $email["to"] . ")"); } else { // complete - invoice is for $0, so don't want to email out log_write("notification", "inc_services_invoicegen", "Invoice {$invoicecode} has not been emailed to the customer due to invoice being for \$0."); } $emailed = "emailed - " . $email["to"]; unset($invoice); } } // end if email enabled } // end if commit successful /* Review Status Here we need to check whether invoicing succeded/failed for the selected customer and process accordingly - we also want to re-set the error flag if running a batch mode, to enable other customers to still be invoiced. */ if (error_check()) { // an error occured $invoice_stats["total_failed"]++; $invoice_stats["failed"][] = array("code_customer" => $customer_data["code_customer"], "name_customer" => $customer_data["name_customer"], "code_invoice" => $invoicecode); // clear the error strings if we are processing from CLI this will allow us to continue on // with additional invoices. if (!empty($_SESSION["mode"])) { if ($_SESSION["mode"] == "cli") { log_write("debug", "inc_services_invoicegen", "Processing from CLI, clearing error flag and continuing with additional invoices"); error_clear(); } } } else { // successful $invoice_stats["total"]++; $invoice_stats["generated"][] = array("code_customer" => $customer_data["code_customer"], "name_customer" => $customer_data["name_customer"], "code_invoice" => $invoicecode, "sent" => $emailed); } // end of success/stats review } // end of processing customers } // end of if customers exist } else { log_debug("inc_services_invoicegen", "No services assigned to customer {$customerid}"); } /* Write Invoicing Report This only takes place if no customer ID is provided, eg we have run a full automatic invoice generation report. */ if ($customerid == NULL) { log_write("debug", "inc_service_invoicegen", "Generating invoice report for invoice generation process"); /* Invoice Stats Calculations */ $invoice_stats["time"] = time() - $invoice_stats["time_start"]; if ($invoice_stats["time"] == 0) { $invoice_stats["time"] = 1; } /* Write Invoice Report */ $invoice_report = array(); $invoice_report[] = "Complete Invoicing Run Time:\t" . $invoice_stats["time"] . " seconds"; $invoice_report[] = "Total Invoices Generated:\t" . $invoice_stats["total"]; $invoice_report[] = "Failed Invoices/Customers:\t" . $invoice_stats["total_failed"]; if (isset($invoice_stats["generated"])) { $invoice_report[] = ""; $invoice_report[] = "Customers / Invoices Generated"; foreach (array_keys($invoice_stats["generated"]) as $id) { $invoice_report[] = " * " . $invoice_stats["generated"][$id]["code_invoice"] . ": " . $invoice_stats["generated"][$id]["code_customer"] . " -- " . $invoice_stats["generated"][$id]["name_customer"]; $invoice_report[] = " [" . $invoice_stats["generated"][$id]["sent"] . "]"; } $invoice_report[] = ""; } if (isset($invoice_stats["failed"])) { $invoice_report[] = ""; $invoice_report[] = "Failed Customers / Invoices"; foreach (array_keys($invoice_stats["failed"]) as $id) { $invoice_report[] = " * " . $invoice_stats["failed"][$id]["code_customer"] . " -- " . $invoice_stats["failed"][$id]["name_customer"]; } $invoice_report[] = ""; $invoice_report[] = "Failed invoices will be attempted again on the following billing run."; $invoice_report[] = ""; } $invoice_report[] = "Invoicing Run Complete"; // display to debug log log_write("debug", "inc_service_invoicegen", "----"); foreach ($invoice_report as $line) { // loop through invoice report lines log_write("debug", "inc_service_invoicegen", $line); } log_write("debug", "inc_service_invoicegen", "----"); // email if appropiate if ($GLOBALS["config"]["ACCOUNTS_INVOICE_BATCHREPORT"] && ($invoice_stats["total"] > 0 || $invoice_stats["total_failed"] > 0)) { log_write("debug", "inc_service_invoicegen", "Emailing invoice generation report to " . $GLOBALS["config"]["ACCOUNTS_EMAIL_ADDRESS"] . ""); /* External dependency of Mail_Mime */ if (!@(include_once 'Mail.php')) { log_write("error", "invoice", "Unable to find Mail module required for sending email"); break; } if (!@(include_once 'Mail/mime.php')) { log_write("error", "invoice", "Unable to find Mail::Mime module required for sending email"); break; } /* Email the Report */ $email_sender = $GLOBALS["config"]["ACCOUNTS_EMAIL_ADDRESS"]; $email_to = $GLOBALS["config"]["ACCOUNTS_EMAIL_ADDRESS"]; $email_subject = "Invoice Batch Process Report"; $email_message = $GLOBALS["config"]["COMPANY_NAME"] . "\n\nInvoice Batch Process Report for " . time_format_humandate() . "\n\n"; foreach ($invoice_report as $line) { $email_message .= $line . "\n"; } // prepare headers $mail_headers = array('From' => $email_sender, 'Subject' => $email_subject); $mail_mime = new Mail_mime("\n"); $mail_mime->setTXTBody($email_message); $mail_body = $mail_mime->get(); $mail_headers = $mail_mime->headers($mail_headers); $mail =& Mail::factory('mail'); $status = $mail->send($email_to, $mail_headers, $mail_body); if (PEAR::isError($status)) { log_write("error", "inc_service_invoicegen", "An error occured whilst attempting to send the batch report email: " . $status->getMessage() . ""); } else { log_write("debug", "inc_service_invoicegen", "Successfully sent batch report email."); } } // end if email } // end if report return 1; }