From ee0d4817b7aa89e031502553e083d51cc6e44e8b Mon Sep 17 00:00:00 2001 From: Balint Morvai Date: Thu, 19 Mar 2015 17:16:23 +0100 Subject: [PATCH] Initial commit --- config/constants.php | 181 ++++++++++++ config/form_validation.php | 22 ++ config/pm.php | 8 + controllers/Pm.php | 339 ++++++++++++++++++++++ db.sql | 89 ++++++ language/english/pm_lang.php | 12 + libraries/Base_model.php | 142 +++++++++ libraries/Table_model.php | 548 +++++++++++++++++++++++++++++++++++ models/Pm_model.php | 392 +++++++++++++++++++++++++ models/User_model.php | 155 ++++++++++ views/compose.php | 78 +++++ views/details.php | 50 ++++ views/list.php | 57 ++++ views/menu.php | 13 + views/welcome_message.php | 89 ++++++ 15 files changed, 2175 insertions(+) create mode 100644 config/constants.php create mode 100644 config/form_validation.php create mode 100644 config/pm.php create mode 100644 controllers/Pm.php create mode 100644 db.sql create mode 100644 language/english/pm_lang.php create mode 100644 libraries/Base_model.php create mode 100644 libraries/Table_model.php create mode 100644 models/Pm_model.php create mode 100644 models/User_model.php create mode 100644 views/compose.php create mode 100644 views/details.php create mode 100644 views/list.php create mode 100644 views/menu.php create mode 100644 views/welcome_message.php diff --git a/config/constants.php b/config/constants.php new file mode 100644 index 0000000..0fc03f0 --- /dev/null +++ b/config/constants.php @@ -0,0 +1,181 @@ + PM_RECIPIENTS, + 'label' => 'Recipients', + 'rules' => 'required' + ), + array( + 'field' => TF_PM_SUBJECT, + 'label' => 'Subject', + 'rules' => 'required' + ), + array( + 'field' => TF_PM_BODY, + 'label' => 'Message Text', + 'rules' => 'required' + ) +); + +/* End of file form_validation.php */ diff --git a/config/pm.php b/config/pm.php new file mode 100644 index 0000000..f82a29a --- /dev/null +++ b/config/pm.php @@ -0,0 +1,8 @@ + 200, + TF_PM_SUBJECT => 100 +); + +/* End of file Pm.php */ diff --git a/controllers/Pm.php b/controllers/Pm.php new file mode 100644 index 0000000..7835262 --- /dev/null +++ b/controllers/Pm.php @@ -0,0 +1,339 @@ +ci = & get_instance(); + $this->config->load('form_validation', TRUE); + $this->config->load('pm', TRUE); + $this->load->helper('url'); + $this->load->library('session'); + $this->load->library('form_validation'); + $this->load->model('Pm_model', 'pm_model'); + $this->load->model('User_model', 'user_model'); + $this->lang->load('pm', 'english'); + } + + /** + * @brief initialize + * + * Initializes values for this class. + * + * @param dateformat string: format to display dates in + * @return void + */ + public function initialize($dateformat = "Y.m.d - H:i:s") + { + $this->pm_model->initialize($dateformat); + } + + /** + * @brief CI index function + * + * CI index function called if no other is specified + * + * @return void + */ + function index() + { + $this->messages(); + } + + /** + * @brief Show a specific message + * + * Show a specific message by message id. + * Views loaded: menu, details. + * Data for 'details' view: $message. + * + * @param msg_id integer: id of the message to get + * @return void + */ + function message($msg_id) + { + if( ! $msg_id) return; + + // Get message and flag it as read + $message = $this->pm_model->get_message($msg_id); + if($message) + { + $message = reset($message); + // Flag message as read + $this->pm_model->flag_read($msg_id); + + // Get recipients & get usernames instead of user ids for recipients and author + $message[TF_PM_AUTHOR] = $this->user_model->get_username($message[TF_PM_AUTHOR]); + $message[PM_RECIPIENTS] = $this->pm_model->get_recipients($message[TF_PM_ID]); + $i = 0; + foreach ($message[PM_RECIPIENTS] as $recipient) + { + $id = $recipient[TF_PMTO_RECIPIENT]; + $message[PM_RECIPIENTS][$i] = $this->user_model->get_username($id); + $i++; + } + $data['message'] = $message; + } + else $data['message'] = array(); + + $this->load->view('menu'); + $this->load->view('details', $data); + } + + /** + * @brief Show messages + * + * Show messages. + * Views loaded: menu, list. + * Data for 'list' view: $messages (associative array|array|string). + * + * @param type integer: message type to get. + * Use one of the following: + * MSG_NONDELETED: received by user, not deleted; + * MSG_DELETED: received by user, deleted; + * MSG_UNREAD: received by user, not deleted, not read; + * MSG_SENT: sent by user (no trashbin, i.e. no deleted state); + * $type < 0: get ALL messages, deleted or not, sent to or by this user; + * @return void + */ + function messages($type = MSG_NONDELETED) + { + // Get & pass to view the messages view type (e.g. MSG_SENT) + $data['type'] = $type; + $messages = $this->pm_model->get_messages($type); + + if($messages) + { + // Get recipients & get usernames instead of user ids + // for recipients and author & render message body correctly + $i = 0; + foreach ($messages as $message) + { + $messages[$i][TF_PM_BODY] = $this->render($messages[$i][TF_PM_BODY]); + $messages[$i][TF_PM_AUTHOR] = $this->user_model->get_username($message[TF_PM_AUTHOR]); + $messages[$i][PM_RECIPIENTS] = $this->pm_model->get_recipients($messages[$i][TF_PM_ID]); + $j = 0; + foreach ($messages[$i][PM_RECIPIENTS] as $recipient) + { + $id = $recipient[TF_PMTO_RECIPIENT]; + $messages[$i][PM_RECIPIENTS][$j] = $this->user_model->get_username($id); + $j++; + } + $i++; + } + $data['messages'] = $messages; + } + else $data['messages'] = array(); + + $this->load->view('menu'); + $this->load->view('list', $data); + } + + /** + * @brief Send message + * + * Send a new message to the users given by username, + * with the given subject and given text body. + * Views loaded: menu, compose. + * Data for 'compose' view: + * $found_recipients (bool), $suggestions (array|string), + * $status (string), $message (associative array|string). + * Flashdata for 'compose' view: 'status'. + * + * @param recipients array|string: array with usernames + * @param subject string: message subject + * @param body string: message text + * @return void + */ + function send($recipients = NULL, $subject = NULL, $body = NULL) + { + $rules = $this->config->item('pm_form', 'form_validation'); + $this->form_validation->set_rules($rules); + + $data['found_recipients'] = TRUE; // assume we'll find recipients - set to FALSE below otherwise + $data['suggestions'] = array(); // if recipients not found by exact search, save suggestions here + $message = array(); + // Set default vals passed via parameters + $message[PM_RECIPIENTS] = $recipients; + $message[TF_PM_SUBJECT] = $subject; + $message[TF_PM_BODY] = $body; + + if($this->form_validation->run()) + { + // Overwrite default vals from params if form validated with vals from POST + $message[PM_RECIPIENTS] = $this->input->post(PM_RECIPIENTS, TRUE); + $message[TF_PM_SUBJECT] = $this->input->post(TF_PM_SUBJECT, TRUE); + $message[TF_PM_BODY] = $this->input->post(TF_PM_BODY, TRUE); + // Lets operate on copies of POST input to preserve orig vals in case of failure + $recipients = explode(";", $this->input->post(PM_RECIPIENTS, TRUE)); + $subject = $this->input->post(TF_PM_SUBJECT, TRUE); + $body = $this->input->post(TF_PM_BODY, TRUE); + + $recipient_ids = array(); + // Get user ids of recipients - if not found, get usernames of suggestions + foreach ($recipients as $recipient) + { + $result = $this->user_model->get_userids(trim($recipient)); + array_push($recipient_ids, reset($result)); + // Try non-exact search if none found to have suggestions - in this case $data['suggestions'] + // will contain an array with original strings as keys & arrays with suggestions as values. + if( ! reset($result)) + { + $data['found_recipients'] = FALSE; + $suggestions = $this->user_model->get_userids(trim($recipient), FALSE); + if($suggestions) + foreach ($suggestions as $suggestion) + $data['suggestions'][$recipient] = $this->user_model->get_username($suggestion); + } + } + + if($data['found_recipients']) + { + if($this->pm_model->send_message($recipient_ids, $subject, $body)) + { + // On success: redirect to list view of messages + $this->session->set_flashdata('status', $this->lang->line('msg_sent')); + redirect($this->base_uri.'messages/'.MSG_NONDELETED); + } + else + { + $status = $this->lang->line('msg_not_sent'); + $this->session->set_flashdata('status', $status); + redirect($this->base_uri.'send/'. + $message[PM_RECIPIENTS].'/'. + $message[TF_PM_SUBJECT].'/'. + $message[TF_PM_BODY]); + } + } + else $data['status'] = $this->lang->line('recipients_not_found'); + } + + // Only happens if sending msg unsuccessful above + if(isset($status)) + { + $data['status'] = $status; + $this->session->set_flashdata('status', $status); + } + $data['message'] = $message; + $this->load->view('menu'); + $this->load->view('compose', $data); + } + + /** + * @brief Delete message + * + * Delete a message from inbox or sent-folder (move to trash). If 3rd parameter + * "redirect" is TRUE, redirect to the view specified by 2nd parameter "type". + * Usually this will be the same view the user deleted the message from. + * Views loaded: - (no view loaded since redirect). + * Flashdata for view redirected to: 'status'. + * + * @param msg_id integer: message to delete by msg id. + * @param type integer: messages view type to redirect to, e.g. MSG_SENT {@link messages}. + * @param redirect bool: indicating whether to redirect to a view after msg deleted. + * @return void + */ + function delete($msg_id, $type = MSG_NONDELETED, $redirect = TRUE) + { + if($msg_id >= 0) + if($this->pm_model->flag_deleted($msg_id)) $status = $this->lang->line('msg_deleted'); + else $status = $this->lang->line('msg_not_deleted'); + else $status = $this->lang->line('msg_invalid_id'); + $this->session->set_flashdata('status', $status); + + // Redirect to $type (e.g. MSG_NONDELETED) view of messages + if($redirect) redirect($this->base_uri.'messages/'.$type); + else $this->session->keep_flashdata('status'); + } + + /** + * @brief Restore message + * + * Restore a message from trash: move to inbox or sent-folder, depending + * on where it was deleted from. The method determines which is correct. + * If 2nd parameter "redirect" is TRUE, redirect to trash view. + * Views loaded: - (no view loaded since redirect). + * Flashdata for view redirected to: 'status'. + * + * @param msg_id integer: message to restore by msg id. + * @param redirect bool: indicating whether to redirect to a view after msg deleted. + * @return void + */ + function restore($msg_id, $redirect = TRUE) + { + if($msg_id >= 0) + if($this->pm_model->flag_undeleted($msg_id)) $status = $this->lang->line('msg_restored'); + else $status = $this->lang->line('msg_not_restored'); + else $status = $this->lang->line('msg_invalid_id'); + $this->session->set_flashdata('status', $status); + + // Redirect to trash bin view of messages + if($redirect) redirect($this->base_uri.'messages/'.MSG_DELETED); + else $this->session->keep_flashdata('status'); + } + + /** + * @brief Render message text + * + * Render the message body text. + * + * @param message_body string: text of the message body to be rendered + * @return string + */ + function render($message_body) + { + $message_body = strip_tags($message_body, ''); + $message_body = stripslashes($message_body); + $message_body = nl2br($message_body); + return $message_body; + } +} + +/* End of file Pm.php */ \ No newline at end of file diff --git a/db.sql b/db.sql new file mode 100644 index 0000000..48130dd --- /dev/null +++ b/db.sql @@ -0,0 +1,89 @@ +-- phpMyAdmin SQL Dump +-- version 3.1.2 +-- http://www.phpmyadmin.net +-- +-- Host: localhost +-- Generation Time: Mar 19, 2015 at 04:37 PM +-- Server version: 5.5.41 +-- PHP Version: 5.5.9-1ubuntu4.6 + +SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; + +-- +-- Database: `mydb` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `privmsgs` +-- + +CREATE TABLE IF NOT EXISTS `privmsgs` ( + `privmsg_id` bigint(20) NOT NULL AUTO_INCREMENT, + `privmsg_author` bigint(20) NOT NULL, + `privmsg_date` varchar(20) NOT NULL, + `privmsg_subject` varchar(1024) NOT NULL, + `privmsg_body` varchar(60000) NOT NULL, + `privmsg_notify` tinyint(1) DEFAULT NULL, + `privmsg_deleted` tinyint(1) DEFAULT NULL, + `privmsg_ddate` varchar(20) DEFAULT NULL, + UNIQUE KEY `privmsg_id` (`privmsg_id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=9 ; + +-- +-- Dumping data for table `privmsgs` +-- + +INSERT INTO `privmsgs` (`privmsg_id`, `privmsg_author`, `privmsg_date`, `privmsg_subject`, `privmsg_body`, `privmsg_notify`, `privmsg_deleted`, `privmsg_ddate`) VALUES +(6, 2, '2015-03-19 12:36:28', 'flowers', 'A flower, sometimes known as a bloom or blossom, is the reproductive structure found in flowering plants (plants of the division Magnoliophyta, also called angiosperms). The biological function of a flower is to effect reproduction, usually by providing a mechanism for the union of sperm with eggs. Flowers may facilitate outcrossing (fusion of sperm and eggs from different individuals in a population) or allow selfing (fusion of sperm and egg from the same flower). Some flowers produce diaspores without fertilization (parthenocarpy). Flowers contain sporangia and are the site where gametophytes develop. Flowers give rise to fruit and seeds. Many flowers have evolved to be attractive to animals, so as to cause them to be vectors for the transfer of pollen.', 1, NULL, NULL), +(7, 1, '2015-03-19 12:38:17', 'Fungi', 'A fungus is any member of a large group of eukaryotic organisms that includes microorganisms such as yeasts and molds (British English: moulds), as well as the more familiar mushrooms. These organisms are classified as a kingdom, Fungi, which is separate from plants, animals, protists, and bacteria. One major difference is that fungal cells have cell walls that contain chitin, unlike the cell walls of plants and some protists, which contain cellulose, and unlike the cell walls of bacteria. These and other differences show that the fungi form a single group of related organisms, named the Eumycota (true fungi or Eumycetes), that share a common ancestor (is a monophyletic group). This fungal group is distinct from the structurally similar myxomycetes (slime molds) and oomycetes (water molds). The discipline of biology devoted to the study of fungi is known as mycology (from the Greek ?????, muk?s, meaning "fungus"). Mycology has often been regarded as a branch of botany, even though it is a separate kingdom in biological taxonomy. Genetic studies have shown that fungi are more closely related to animals than to plants.', 1, NULL, NULL), +(8, 2, '2015-03-19 12:39:05', 'Bacteria', 'Bacteria constitute a large domain of prokaryotic microorganisms. Typically a few micrometres in length, bacteria have a number of shapes, ranging from spheres to rods and spirals. Bacteria were among the first life forms to appear on Earth, and are present in most of its habitats. Bacteria inhabit soil, water, acidic hot springs, radioactive waste,[4] and the deep portions of Earth's crust. Bacteria also live in symbiotic and parasitic relationships with plants and animals. They are also known to have flourished in manned spacecraft.[5]', 1, NULL, NULL); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `privmsgs_to` +-- + +CREATE TABLE IF NOT EXISTS `privmsgs_to` ( + `pmto_id` bigint(20) NOT NULL AUTO_INCREMENT, + `pmto_message` bigint(20) NOT NULL, + `pmto_recipient` bigint(20) NOT NULL, + `pmto_read` tinyint(1) DEFAULT NULL, + `pmto_rdate` varchar(20) DEFAULT NULL, + `pmto_deleted` tinyint(1) DEFAULT NULL, + `pmto_ddate` varchar(20) DEFAULT NULL, + `pmto_allownotify` tinyint(1) DEFAULT NULL, + UNIQUE KEY `pmto_id` (`pmto_id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=8 ; + +-- +-- Dumping data for table `privmsgs_to` +-- + +INSERT INTO `privmsgs_to` (`pmto_id`, `pmto_message`, `pmto_recipient`, `pmto_read`, `pmto_rdate`, `pmto_deleted`, `pmto_ddate`, `pmto_allownotify`) VALUES +(5, 6, 1, NULL, NULL, NULL, NULL, NULL), +(6, 7, 2, NULL, NULL, NULL, NULL, NULL), +(7, 8, 1, NULL, NULL, NULL, NULL, NULL); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `users` +-- + +CREATE TABLE IF NOT EXISTS `users` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `username` varchar(50) COLLATE utf8_bin NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=3 ; + +-- +-- Dumping data for table `users` +-- + +INSERT INTO `users` (`id`, `username`) VALUES +(1, 'FooBar'), +(2, 'FooFoo'); + diff --git a/language/english/pm_lang.php b/language/english/pm_lang.php new file mode 100644 index 0000000..5890390 --- /dev/null +++ b/language/english/pm_lang.php @@ -0,0 +1,12 @@ +ci = & get_instance(); + } + + /** + * @brief convert_mysql_type + * + * Function to convert a mysql field type specification string to a + * local field type TFIELD_*. + * + * @param field_type string: field type specification string + * @param field_max_length int: maximum field length + * @return int local field type TFIELD_* + */ + public function convert_mysql_type($field_type, $field_max_length = -1) + { + $field_type = strtolower($field_type); + if($tmp_pos = strpos($field_type, '(')) $field_type = substr($field_type, 0, $tmp_pos); + if($tmp_pos = strpos($field_type, '[')) $field_type = substr($field_type, 0, $tmp_pos); + switch($field_type) + { + case 'bit': + $type = TFIELD_BOOL; + break; + case 'tinyint': + if($field_max_length == 1) $type = TFIELD_BOOL; + else $type = TFIELD_INT; + break; + case 'smallint': + case 'mediumint': + case 'int': + $type = TFIELD_INT; + break; + case 'bigint': + case 'float': + case 'double': + case 'decimal': + $type = TFIELD_FLOAT; + break; + case 'char': + case 'varchar': + case 'tinytext': + case 'text': + case 'mediumtext': + case 'longtext': + case 'binary': + case 'varbinary': + case 'tinyblob': + case 'mediumblob': + case 'blob': + case 'longblob': + case 'enum': + case 'set': + $type = TFIELD_STR; + break; + case 'date': + case 'datetime': + case 'time': + case 'timestamp': + case 'year': + $type = TFIELD_DATE; + break; + default: + $type = TFIELD_DEFAULT; + break; + } + + return $type; + } + + /** + * @brief is_function_defined + * + * Function checking whether the (mysql) db contains the given costum + * function definition. + * + * @param function_name string: name of the function to be checked + * @return bool + */ + public function is_function_defined($function_name) + { + $passed = TRUE; + + $this->db->select('ROUTINE_NAME'); + $this->db->from('INFORMATION_SCHEMA.ROUTINES'); + $this->db->where("`ROUTINE_TYPE` = 'FUNCTION' AND + `ROUTINE_SCHEMA` = '".$this->db->database."' AND + `ROUTINE_NAME` = '$function_name'"); + $query = $this->db->get(); + if($query->num_rows() == 0) + $passed = FALSE; + + return $passed; + } +} + +/* End of file Base_model.php */ diff --git a/libraries/Table_model.php b/libraries/Table_model.php new file mode 100644 index 0000000..320e380 --- /dev/null +++ b/libraries/Table_model.php @@ -0,0 +1,548 @@ +name = $_name; + $this->enforce_field_types = $_enforce_field_types; + $this->ci = & get_instance(); + $this->ci->load->database(); + + if($this->name) + { + if( ! $this->ci->db->table_exists($this->name)) + { + if($this->name) log_message('error', "db error: table not found by name '{$_name}'"); + return FALSE; + } + // get db field info + $this->fields = $this->ci->db->field_data($this->name); + // define types: convert db types to own local types + foreach ($this->fields as $field) + { + $this->ci->load->library('Base_model'); + $type = $this->ci->base_model->convert_mysql_type($field->type, $field->max_length); + $this->types[$field->name] = $type; + } + // define data fields + $this->fields = array_values($this->ci->db->list_fields($this->name)); + } + } + + /** + * @brief initialize + * + * Initializes values for this class. + * Resets the results array to maintain consistency of field types. + * + * @param _dateformat string: format to display dates in + * @param _enforce_field_types bool: setting whether to enforce field types in PHP by cast + * @return void + */ + public function initialize($_dateformat = "d.m.Y - H:i", $_enforce_field_types = FALSE) + { + // Define the date format + $this->dateformat = $_dateformat; + // Define whether to enforce db field types in PHP by type cast + $this->enforce_field_types = $_enforce_field_types; + // Since we change "enforce_field_types" reset results array + $this->results = array(); + } + + /** + * @brief Check whether test var is a valid db index + * + * Function checking whether the supplied var is a valid db index. + * Therefore it has to be (convertible to) a positive integer or zero. + * + * @param testvar int: var to check if it is valid index number + * @return bool + */ + public function is_valid_index($testvar) + { + $passed = FALSE; + + // count number of correct fields in "data" + if(ctype_digit($testvar) OR is_int($testvar)) + if((int)$testvar >= 0) + $passed = TRUE; + + return $passed; + } + + /** + * @brief Get types array + * + * Function returning the (protected) {@link types} array. + * + * @return array + */ + public function get_types() + { + return $this->types; + } + + /** + * @brief Get fields array + * + * Function returning the (protected) {@link fields} array. + * + * @return array + */ + public function get_fields() + { + return $this->fields; + } + + /** + * @brief Get name string + * + * Function returning the (protected) {@link name} string. + * + * @return string + */ + public function get_name() + { + return $this->name; + } + + /** + * @brief Check whether data array is valid db array + * + * Function checking whether the supplied data array is equal to the ones returned from + * the given table this base model is connected to. Therefore it has to be an associative + * array containing all the same table field names as keys as the result arrays + * returned by CI. + * + * @param data array: associative array with table field names as keys to be checked if it + * is equal to result arrays returned by CI from this model's table + * @return bool + */ + public function is_valid_data($data) + { + $passed = TRUE; + + if( ! $this->is_valid_subdata($data) OR (count($data) != count($this->fields))) + $passed = FALSE; + + return $passed; + } + + /** + * @brief Check whether data array is valid db array subset + * + * Function checking whether the supplied data array is a partial data from + * the given table this base model is connected to. Therefore it has to be an + * associative array containing one or more table field names as keys but no + * keys that are not table field names. Since it does not have to contain + * ALL the data fields it is only a partial- or subdata array. + * + * @param data array: associative array with table field names as keys to be checked if it + * is a subdata from this model's table + * @return bool + */ + public function is_valid_subdata($data) + { + $passed = TRUE; + + // count number of correct fields in "data" + if( ! empty($data) AND is_array($data)) + { + foreach ($data as $key => $value) + if( ! in_array($key, $this->fields)) + $passed = FALSE; + } + else $passed = FALSE; + + return $passed; + } + + /** + * @brief Get data from db + * + * Function that either executes db->get() if "query_string" is FALSE (executing the + * currently set query) OR executes db->query($query_string) if "query_string" is given + * and returns data as "result_array" as well as puts them in the {@link results} var. + * + * Use it to retrieve data (rows) from db. If you dont supply a query string set query + * specifics before call using CI functions like db->select, db->from, db->where,... + * Use the {@link get_types} function to find out about the table structure. + * + * Returns FALSE on error, array otherwise + * + * @param query_string string: query string to be executed using the CI "query" function + * instead of just using the CI "get()" function. When FALSE "get()" is used. + * @return array + */ + public function get_data($query_string = FALSE) + { + // get data depending on whether query string is given with "query" or "get" + try + { + if($query_string) + $data = $this->ci->db->query($query_string); + else + $data = $this->ci->db->get(); + } + catch(Exception $e) + { + log_message('error', "db error: error executing query to get matching data ({$e->getMessage()})"); + return FALSE; + } + + $data = $data->result_array(); + $this->results = $data; + // if desired, convert each data field to its correct PHP type + if(( ! empty($data))) + { + if($this->enforce_field_types) + { + foreach ($data as $index => $row) + { + foreach ($row as $field => $value) + { + // convert to types + switch($this->types[$field]) + { + case TFIELD_FLOAT: + $this->results[$index][$field] = (float)$value; + break; + case TFIELD_INT: + $this->results[$index][$field] = (int)$value; + break; + case TFIELD_BOOL: + $this->results[$index][$field] = (bool)$value; + break; + case TFIELD_DATE: + $this->results[$index][$field] = date($this->dateformat, strtotime($value)); + break; + case TFIELD_STR: + $this->results[$index][$field] = $value; + break; + } + } + } + } + else // if enforce_field_types FALSE at least convert date fields + foreach ($data as $index => $row) + foreach ($row as $field => $value) + if($this->types[$field] == TFIELD_DATE) + $this->results[$index][$field] = date($this->dateformat, strtotime($value)); + } + + return $this->results; + } + + + /** + * @brief Complete sub-data array with data from db + * + * Function that either executes db->get() if "query_string" is FALSE (executing the + * currently set query) OR executes db->query($query_string) if "query_string" is set. + * The "query_string" or preset query should select exactly one table row, the one to + * be used for completion of the given subdata array. If more than one row is returned, + * only the first row is used. + * The function gets the complete result array of the selected row from the table + * and completes the given data array with its values and keys: it inserts (only) + * missing keys with its values. + * The "data" param has to be a valid subdata array and it is checked therefore. + * Returns array if successful and empty array if no results and FALSE on error. + * + * Use the {@link get_types} function to build a valid data array easily. + * + * @param subdata array: associative array with db field names as keys and values as values + * as data in {@link results} var. Checked if valid subdata. + * @param query_string string: query string to be executed using the CI "query" function + * instead of just using the CI "get()" function. When FALSE "get()" is used. + * @return array + */ + public function complete_data($subdata, $query_string = FALSE) + { + // check if given data array is a valid (partial) data + if( ! $this->is_valid_subdata($subdata)) + { + log_message('error', 'error: data array not valid)'); + return FALSE; + } + + try + { + if($query_string) + $data = $this->ci->db->query($query_string); + else + $data = $this->ci->db->get(); + } + catch(Exception $e) + { + log_message('error', "db error: error executing query to get matching data ({$e->getMessage()})"); + return FALSE; + } + + $data = $data->result_array(); + + // if desired, convert each data field to its correct PHP type + if( ! empty($data)) + { + $data = reset($data); + if($this->enforce_field_types) + { + foreach ($this->types as $field => $type) + { + // convert to types + switch($type) + { + case TFIELD_FLOAT: + $data[$field] = (float)$data[$field]; + break; + case TFIELD_INT: + $data[$field] = (int)$data[$field]; + break; + case TFIELD_BOOL: + $data[$field] = (bool)$data[$field]; + break; + case TFIELD_DATE: + $data[$field] = date($this->dateformat, strtotime($data[$field])); + break; + case TFIELD_STR: + $data[$field] = $data[$field]; + break; + } + } + } + } + else + { + log_message('error', "db error: getting row from db for completion of given subdata failed"); + return array(); + } + + // complete given data array with table data array + foreach ($data as $field => $value) + if( ! array_key_exists($field, $subdata)) + $subdata[$field] = $value; + + return $subdata; + } + + /** + * @brief Insert data to db + * + * Function that executes db->insert($this->name) either without data array if "data" param + * is FALSE (executing the (pre)set query) OR executes db->insert($this->name, $data) + * if "data" param is set. If handed over, the "data" param has to be a valid subdata + * array and it is checked therefore. Fields missing have to have default values in the db. + * Also sets {@link insert_id}, sets it to FALSE on failure. Returns TRUE if insert + * successful and FALSE otherwise. + * + * Use the {@link get_types} function to build a valid data array easily. + * + * @param data array: associative array with db field names as keys and values as values + * as data in {@link results} var. Checked if valid subdata. + * @return bool + */ + public function insert_data($data = FALSE) + { + // check if given data array is a valid (partial) data + if($data) + { + if( ! $this->is_valid_subdata($data)) + { + log_message('error', 'error: data array not valid)'); + return FALSE; + } + } + + try + { + if($data) + $this->ci->db->insert($this->name, $data); + else + $this->ci->db->insert($this->name); + + $this->insert_id = $this->ci->db->insert_id(); + } + catch(Exception $e) + { + log_message('error', "db error: failed to insert data ({$e->getMessage()})"); + return FALSE; + } + + return TRUE; + } + + /** + * @brief Update data in db + * + * Function that executes db->update($this->name) either without data array if "data" param + * is FALSE (executing the (pre)set query) OR executes db->update($this->name, $data) + * if "data" param is set. If handed over, the "data" param has to be a valid subdata + * array and it is checked therefore. NOTE: in both cases a correct "db->where" query + * HAS TO BE SET BEFORE CALLING! The row to update is specified by this where query + * and NOT by the "data" array! + * Returns TRUE if update successful and FALSE if otherwise. + * + * Use the {@link get_types} function to build a valid data array easily. + * + * @param data array: associative array with db field names as keys and values as values + * as data in {@link results} var. Checked if valid subdata. + * @return bool + */ + public function update_data($data = FALSE) + { + // check if given data array is a valid (partial) data + if($data) + { + if( ! $this->is_valid_subdata($data)) + { + log_message('error', 'error: data array not valid)'); + return FALSE; + } + } + + try + { + if($data) + $this->ci->db->update($this->name, $data); + else + $this->ci->db->update($this->name); + } + catch(Exception $e) + { + log_message('error', "db error: failed to update data ({$e->getMessage()}), ". + 'did you forget to set a correct where query before call?'); + return FALSE; + } + + return TRUE; + } + + /** + * @brief Delete data in db + * + * Function that executes db->delete($this->name) either without id array if "ids" param + * is FALSE (executing the (pre)set query) OR executes db->delete($this->name, $ids) + * if "ids" param is set. If handed over, the "ids" param has to be a valid subdata + * array and it is checked therefore - more specifically it'll mostly contain only + * the ids to delete: array('id_field_name' => $id1, 'id_field_name' => $id2, ...) + * + * Returns TRUE if delete successful and FALSE if otherwise. + * + * Use the {@link get_types} function to build a valid data array easily. + * + * @param ids array: associative array with db field names as keys and values as values + * as data in {@link results} var. Checked if valid subdata. + * @return bool + */ + public function delete_data($ids = FALSE) + { + // check if given ids array is a valid (partial) data array + if($ids) + { + if( ! $this->is_valid_subdata($ids)) + { + log_message('error', 'error: ids array not valid)'); + return FALSE; + } + } + + try + { + if($ids) + $this->ci->db->delete($this->name, $ids); + else + $this->ci->db->delete($this->name); + } + catch(Exception $e) + { + log_message('error', "db error: failed to delete data ({$e->getMessage()})"); + return FALSE; + } + + return TRUE; + } +} + +/* End of file Table_model.php */ diff --git a/models/Pm_model.php b/models/Pm_model.php new file mode 100644 index 0000000..9dd5b6a --- /dev/null +++ b/models/Pm_model.php @@ -0,0 +1,392 @@ +ci = & get_instance(); + $this->load->model('User_model', 'user_model'); + $this->load->library('Table_model'); + $this->table1 = new Table_model(TABLE_PM, $dateformat, $enforce_field_types); + $this->table2 = new Table_model(TABLE_PMTO, $dateformat, $enforce_field_types); + $this->user_id = $this->user_model->current_id(); + } + + /** + * @brief initialize + * + * Initializes values for this class. + * + * @param dateformat string: format to display dates in + * @param enforce_field_types bool: setting whether to enforce field types in PHP by cast + * @return void + */ + public function initialize($dateformat = "d.m.Y - H:i", $enforce_field_types = TRUE) + { + // Define the date format & whether db field types are enforced in PHP by type cast + $this->table1->initialize($dateformat, $enforce_field_types); + $this->table2->initialize($dateformat, $enforce_field_types); + } + + /** + * @brief Get messages + * + * Get messages to or from the logged in user and return CI results + * array. Get messages of the given type, see below. Order results + * by date created, descending. + * + * @param type integer: message type to get. Use one of the following: + * MSG_NONDELETED: received by user, not deleted (default); + * MSG_DELETED: received or sent by user, deleted; + * MSG_UNREAD: received by user, not deleted, not read; + * MSG_SENT: sent by user, not deleted; + * type < 0: get ALL messages, deleted or not, sent to or by this user + * @return array + */ + public function get_messages($type = MSG_NONDELETED) + { + // Lets use abbreviations + $t1 = $this->table1->get_name(); + $t2 = $this->table2->get_name(); + + $this->db->select($t1.'.*'); + $this->db->from($t1); + // Specify what type of messages you want to get - conditions work with join; + // Since db evaluates "AND" first "A AND B OR C AND D" = "(A AND B) OR (C AND D)" + switch($type) + { + // Message types RECEIVED + case MSG_NONDELETED: + $this->db->where(TF_PMTO_RECIPIENT, $this->user_id); + $this->db->where(TF_PMTO_DELETED, NULL); + break; + case MSG_DELETED: + // this produces "(A AND B) OR (C AND D)" + $this->db->where(TF_PMTO_RECIPIENT, $this->user_id); + $this->db->where(TF_PMTO_DELETED, 1); + $this->db->or_where(TF_PM_AUTHOR, $this->user_id); + $this->db->where(TF_PM_DELETED, 1); + break; + case MSG_UNREAD: + $this->db->where(TF_PMTO_RECIPIENT, $this->user_id); + $this->db->where(TF_PMTO_DELETED, NULL); + $this->db->where(TF_PMTO_READ, NULL); + break; + // Message type SENT + case MSG_SENT: + $this->db->where(TF_PM_AUTHOR, $this->user_id); + $this->db->where(TF_PM_DELETED, NULL); + break; + // Message type RECEIVED OR SENT (deleted or not, sent to or by this user) + default: + $this->db->where(TF_PMTO_RECIPIENT, $this->user_id); + $this->db->where(TF_PM_AUTHOR, $this->user_id); + break; + } + // Get messages by join of table1 & 2 + $this->db->join($t2, TF_PMTO_MESSAGE.' = '.TF_PM_ID); + $this->db->group_by(TF_PM_ID); // To get only distinct messages + $this->db->order_by(TF_PM_DATE, 'desc'); + + return $this->table1->get_data(); + } + + /** + * @brief Get message + * + * Get a specific message by message id to or from the logged in user + * and return CI results array. + * + * @param msg_id integer: message id of the message to get + * @return array + */ + public function get_message($msg_id) + { + // Lets use abbreviations + $t1 = $this->table1->get_name(); + $t2 = $this->table2->get_name(); + + // Get message by join of table1 & 2 + $this->db->select($t1.'.*'); + $this->db->from($t1); + // this produces "(A AND B) OR (A AND C)" = "A AND (B OR C)" + $this->db->where(TF_PM_ID, $msg_id); + $this->db->where(TF_PMTO_RECIPIENT, $this->user_id); + $this->db->or_where(TF_PM_ID, $msg_id); + $this->db->where(TF_PM_AUTHOR, $this->user_id); + $this->db->join($t2, TF_PMTO_MESSAGE.' = '.TF_PM_ID); + + return $this->table1->get_data(); + } + + /** + * @brief Get recipients + * + * Get user ids of all recipients of a specific message given by msg + * id. Do that for any message, not just the ones authored by the + * logged in user. + * Returns CI result array with recipient ids or empty array. + * + * @param msg_id integer: message id of the message to get recipients for + * @return array + */ + public function get_recipients($msg_id) + { + // Lets use abbreviations + $t2 = $this->table2->get_name(); + + // Get recipients from table2 + $this->db->select(TF_PMTO_RECIPIENT); + $this->db->from($t2); + $this->db->where(TF_PMTO_MESSAGE, $msg_id); + + return $this->table2->get_data(); + } + + /** + * @brief Get author + * + * Get user id of author of a specific message given by msg + * id. Do that for any message, not just the ones authored by the + * logged in user. + * Returns user id directly if msg found or -1 otherwise. + * + * @param msg_id integer: message id of the message to get author for + * @return int + */ + public function get_author($msg_id) + { + $message = $this->get_message($msg_id); + if($message) + { + $message = reset($message); + $author = $message[TF_PM_AUTHOR]; + } + else + $author = -1; + + return $author; + } + + /** + * @brief Flag read + * + * Flag a message (by id) as read. If optional 2nd param is set + * FALSE, the sender will not get to know that msg was read. + * Returns TRUE if successful, FALSE otherwise. + * + * @param msg_id integer: db message id of the message to flag as read + * @param allow_notify bool: boolean indicating whether author may be notified if requested + * @return bool + */ + function flag_read($msg_id, $allow_notify = TRUE) + { + // Lets use abbreviations + $t2 = $this->table2->get_name(); + + $this->db->set(TF_PMTO_READ, 1); + $this->db->set(TF_PMTO_RDATE, 'NOW()', FALSE); + if($allow_notify) $this->db->set($t2.'.'.TF_PMTO_ALLOWNOTIFY, 1); + $this->db->limit(1, 0); + $this->db->where(TF_PMTO_MESSAGE, $msg_id); + $this->db->where(TF_PMTO_RECIPIENT, $this->user_id); + + return $this->table2->update_data(); + } + + /** + * @brief Flag message deleted + * + * Flag a message (by id) as deleted. + * Note: depending on whether the user was recipient or author the + * message will be flaged deleted in table2 or table1, i.e. it will + * be determined automatically if the msg is to be deleted from + * sent-folder or inbox of the user. + * Optionally through 2nd param a costum value can be supplied to + * update the "deleted" field to, while the default is 1. This also + * can be used to restore the msg instead of deleting it, by e.g. + * passing NULL. + * NOTE: The "DDATE" will be set to "NOW" regardeless of the "status" + * value passed. + * Returns TRUE if successful, FALSE otherwise. + * + * @param msg_id integer: db message id of the message to flag as deleted + * @param status integer: optional value to update "deleted" field to, default 1 + * @return bool + */ + function flag_deleted($msg_id, $status = 1) + { + $this->db->limit(1, 0); + if($this->get_author($msg_id) == $this->user_id) + { + $this->db->set(TF_PM_DELETED, $status); + $this->db->set(TF_PM_DDATE, 'NOW()', FALSE); + $this->db->where(TF_PM_ID, $msg_id); + $this->db->where(TF_PM_AUTHOR, $this->user_id); + return $this->table1->update_data(); + } + else + { + $this->db->set(TF_PMTO_DELETED, $status); + $this->db->set(TF_PMTO_DDATE, 'NOW()', FALSE); + $this->db->where(TF_PMTO_MESSAGE, $msg_id); + $this->db->where(TF_PMTO_RECIPIENT, $this->user_id); + return $this->table2->update_data(); + } + } + + /** + * @brief Flag message undeleted + * + * Flag a message (by id) as NOT deleted. + * Note: This method is just using the {@link flag_deleted} method + * with "NULL" as 2nd param. This will make the DDATE be the + * "restored date". + * Returns TRUE if successful, FALSE otherwise. + * + * @param msg_id integer: db message id of the message to flag as deleted + * @return bool + */ + function flag_undeleted($msg_id) + { + return $this->flag_deleted($msg_id, NULL); + } + + /** + * @brief Send message + * + * Add a new personal message to table1 and recipients to table2. + * Note: sending messages to oneself is not allowed and this should + * stay this way, since it would cause problems with deleting & + * restoring messages. + * Returns TRUE if successful, returns FALSE otherwise. + * + * @param recipients integer: array of one or more user ids of the recipients + * (can be array or single var) of the message to add. + * @param subject string: subject of the message + * @param body string: message text + * @param notify bool: notify flag, whether to notify sender upon read, default TRUE + * @return bool + */ + function send_message($recipients, $subject, $body, $notify = TRUE) + { + // Check notify + if( ! $notify) $notify = NULL; + else $notify = TRUE; + // Check recipients + if( ! is_array($recipients)) $recipients = array($recipients); + + foreach ($recipients as $recipient) + if( ! $this->user_model->table1->is_valid_index($recipient)) + return FALSE; + + // insert message in table1 + $this->db->set(TF_PM_AUTHOR, $this->user_id); + $this->db->set(TF_PM_DATE, 'NOW()', FALSE); + $this->db->set(TF_PM_SUBJECT, $subject); + $this->db->set(TF_PM_BODY, $body); + $this->db->set(TF_PM_NOTIFY, $notify); + if( ! $this->table1->insert_data()) + return FALSE; + $msg_id = $this->table1->insert_id; + + // insert links to it for recipients in table2 + $failed = FALSE; // if sth. fails here, more complex cleanup is required + foreach ($recipients as $recipient) + { + // Do not allow sending messages to oneself! + if($recipient != $this->user_id) + { + $this->db->set(TF_PMTO_MESSAGE, $msg_id); + $this->db->set(TF_PMTO_RECIPIENT, $recipient); + if( ! $this->table2->insert_data()) + $failed = TRUE; + } + } + // on failure remove all we just inserted & return FALSE + if($failed) + { + $this->table1->delete_data(array(TF_PM_ID => $msg_id)); + $this->table2->delete_data(array(TF_PMTO_MESSAGE, $msg_id)); + return FALSE; + } + + return TRUE; + } +} + +/* End of file Pm_model.php */ diff --git a/models/User_model.php b/models/User_model.php new file mode 100644 index 0000000..2da71fd --- /dev/null +++ b/models/User_model.php @@ -0,0 +1,155 @@ +load->library('Table_model'); + $this->table1 = new table_model(TABLE_USERS, $dateformat, $enforce_field_types); + } + + /** + * @brief initialize + * + * Initializes values for this class. + * + * @param dateformat string: format to display dates in + * @param enforce_field_types bool: setting whether to enforce field types in PHP by cast + * @return void + */ + public function initialize($dateformat = "d.m.Y - H:i", $enforce_field_types = TRUE) + { + // Define the date format & whether db field types are enforced in PHP by type cast + $this->table1->initialize($dateformat, $enforce_field_types); + } + + /** + * @brief Get user id from a username + * + * Get user id from a username - gets any users id. + * Per default performs an exact match search and returns + * 1 or 0 ids in enumerated array! (not CI result array) + * If you want a fuzzy search pass 2nd parameter FALSE. This will + * search for any name containing given string and return ids - the + * max number of returned ids (in the case of fuzzy search) is + * limited by the 3rd optional parameter. + * Returns an enumerated array in any case, containing 1 or more user + * ids or empty if no matches found. + * + * @param username string: username to get user id(s) for + * @param exact bool: if TRUE, exact search, returns 1 or 0 user ids in array; + * if FALSE, fuzzy search, return 1 or more user ids in array + * @param max_id_count int: max number of ids returned if fuzzy search done + * @return array + */ + public function get_userids($username, $exact = TRUE, $max_id_count = 10) + { + $this->db->select(TF_USER_ID); + $this->db->from($this->table1->get_name()); + if($exact) + { + $this->db->where(TF_USER_NAME, $username); + $this->db->limit(1, 0); + } + else + { + $this->db->like(TF_USER_NAME, $username); + $this->db->limit($max_id_count, 0); + } + + $retval = array(); + if($res = $this->table1->get_data()) + foreach($res as $row) + array_push($retval, $row[TF_USER_ID]); + + return $retval; + } + + /** + * @brief Get user name from an id. + * + * Get user name from an id - gets any users name, not just logged + * in users. + * Returns a string with the username. + * + * @param id int: user id to get user name for + * @return string + */ + public function get_username($id) + { + $this->db->select(TF_USER_NAME); + $this->db->from($this->table1->get_name()); + $this->db->where(TF_USER_ID, $id); + $this->db->limit(1, 0); + + $retval = ''; + if($res = $this->table1->get_data()) + $retval = $res[0][TF_USER_NAME]; + + return $retval; + } + + /** + * @brief dummy method returning first user id found + * + * !!! DUMMY METHOD - IMPLEMENT THIS AS NEEDED !!! + * Get user id of current user. + * !!! DUMMY METHOD - IMPLEMENT THIS AS NEEDED !!! + * + * @return int + */ + public function current_id() + { + $this->db->select(TF_USER_ID); + $this->db->from($this->table1->get_name()); + $this->db->limit(1, 0); + + $retval = -1; + if($res = $this->table1->get_data()) + $retval = $res[0][TF_USER_ID]; + + return $retval; + } +} + +/* End of file User_model.php */ diff --git a/views/compose.php b/views/compose.php new file mode 100644 index 0000000..df0ea93 --- /dev/null +++ b/views/compose.php @@ -0,0 +1,78 @@ +load->library('session'); +$MAX_INPUT_LENGTHS = $this->config->item('$MAX_INPUT_LENGTHS', 'pm'); +$recipients = array( + 'name' => PM_RECIPIENTS, + 'id' => PM_RECIPIENTS, + 'value' => set_value(PM_RECIPIENTS, $message[PM_RECIPIENTS]), + 'maxlength' => $MAX_INPUT_LENGTHS[PM_RECIPIENTS], + 'size' => 40, +); +$subject = array( + 'name' => TF_PM_SUBJECT, + 'id' => TF_PM_SUBJECT, + 'value' => set_value(TF_PM_SUBJECT, $message[TF_PM_SUBJECT]), + 'maxlength' => $MAX_INPUT_LENGTHS[TF_PM_SUBJECT], + 'size' => 40 +); +$body = array( + 'name' => TF_PM_BODY, + 'id' => TF_PM_BODY, + 'value' => set_value(TF_PM_BODY, $message[TF_PM_BODY]), + 'cols' => 80, + 'rows' => 5 +); +?> + +uri->uri_string()); ?> + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + session->flashdata('status')) echo $this->session->flashdata('status').' '; + if(!$found_recipients) + { + foreach($suggestions as $original => $suggestion) + { + echo 'Did you mean '.$suggestion.' for '.$original.' ?'; + echo '
'; + } + } ?> +
+ diff --git a/views/details.php b/views/details.php new file mode 100644 index 0000000..2240bbe --- /dev/null +++ b/views/details.php @@ -0,0 +1,50 @@ +0):?> + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Subject + + +
+ From + + +
+ To + + +
+ Date + + +
+ Message + + +
+ +

No message found.

+ diff --git a/views/list.php b/views/list.php new file mode 100644 index 0000000..2f76b06 --- /dev/null +++ b/views/list.php @@ -0,0 +1,57 @@ +0):?> + + + + + + + + + + + + + + + + + + + + + + +
+ + + Subject + + Date + + Reply + + +
+ + + '> + + + + reply ' ?> + + x '; + else + echo ' o '; ?> +
+ +

No messages found.

+ diff --git a/views/menu.php b/views/menu.php new file mode 100644 index 0000000..89bb2b0 --- /dev/null +++ b/views/menu.php @@ -0,0 +1,13 @@ +load->helper('url'); ?> + + + + +
+ ">Inbox     + ">Unread     + ">Sent     + ">Trashed     + ">Compose        +
+
\ No newline at end of file diff --git a/views/welcome_message.php b/views/welcome_message.php new file mode 100644 index 0000000..f511563 --- /dev/null +++ b/views/welcome_message.php @@ -0,0 +1,89 @@ + + + + + Welcome to CodeIgniter + + + + + +
+

Welcome to CodeIgniter!

+ +
+

The page you are looking at is being generated dynamically by CodeIgniter.

+ +

If you would like to edit this page you'll find it located at:

+ application/views/welcome_message.php + +

The corresponding controller for this page is found at:

+ application/controllers/Welcome.php + +

If you are exploring CodeIgniter for the very first time, you should start by reading the User Guide.

+
+ + +
+ + + \ No newline at end of file