expense_tracker-1.2.1/expense_tracker.module
expense_tracker.module
<?php
use Drupal\Core\Template\Attribute;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\Core\Link;
use Drupal\Core\Form\FormStateInterface;
use Drupal\expense_tracker\Entity\EtTransaction;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Ajax\InvokeCommand;
use Drupal\Core\Entity\EntityFormInterface;
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Drupal\Core\Entity\EntityInterface;
/**
* Denotes that the expense_tracker is not published.
*/
const POLL_NOT_PUBLISHED = 0;
/**
* Denotes that the expense_tracker is published.
*/
const POLL_PUBLISHED = 1;
/**
* Implements hook_help().
*/
function expense_tracker_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.expense_tracker':
$output = '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The EtTransaction helps you easily see all your expenses and income in one place. Keep track of all your transactions and reports will help you understand your spending habits. For more information, see the online handbook entry for the <a href=":expense_tracker">EtTransaction module</a>.', array(':expense_tracker' => 'https://www.drupal.org/docs/8/modules/expense_tracker')) . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<dl>';
$output .= '<dt>' . t('Creating an expense and income transactions') . '</dt>';
$output .= '</dl>';
return $output;
}
}
/**
* Implements hook_theme().
*/
function expense_tracker_theme() {
$theme_hooks = array(
'income_reports' => array(
'template' => 'income-reports',
'variables' => array(
'reports_data' => NULL,
'tabs' => NULL,
'other_data' => NULL
),
),
'income_expense_reports' => array(
'template' => 'income-expense-reports',
'variables' => array(
'reports_data' => NULL,
'tabs' => NULL,
'other_data' => NULL
),
),
);
return $theme_hooks;
}
/**
* Implements hook_page_attachments().
*/
function expense_tracker_page_attachments(array &$page) {
$page['#attached']['library'][] = 'expense_tracker/drupal.expense_tracker-links';
}
/**
* Implements hook_cron().
*/
function expense_tracker_cron() {
// repeat transactions with cron
repeat_transactions();
}
/**
* Implements hook_form_alter().
*/
function expense_tracker_form_alter(&$form, FormStateInterface $form_state, $form_id) {
// Adding custom validation for the welcome page type field.
if ($form_id == 'et_transaction_form' || $form_id == 'et_transaction_edit_form') {
$form['#attached']['library'][] = 'expense_tracker/admin';
$form['category']['#states'] = array(
'visible' => array(
':input[name="transaction_category"]' => array('value' => 'existing'),
),
'required' => [
':input[name="transaction_category"]' => ['value' => 'existing'],
],
);
$form['repeat_every']['#states'] = array(
'visible' => array(
':input[name="repeat[value]"]' => ['checked' => TRUE],
),
'required' => [
':input[name="repeat[value]"]' => ['checked' => TRUE],
],
);
$form['specific_week_days']['#states'] = array(
'visible' => array(
':input[name="repeat[value]"]' => ['checked' => TRUE],
':input[name="repeat_every"]' => ['value' => 'specific_week_days']
),
'required' => [
':input[name="repeat[value]"]' => ['checked' => TRUE],
':input[name="repeat_every"]' => ['value' => 'specific_week_days']
],
);
$form['specific_month_days']['#states'] = array(
'visible' => array(
':input[name="repeat[value]"]' => ['checked' => TRUE],
':input[name="repeat_every"]' => ['value' => 'specific_month_days']
),
'required' => [
':input[name="repeat[value]"]' => ['checked' => TRUE],
':input[name="repeat_every"]' => ['value' => 'specific_month_days']
],
);
$form['end_date']['#states'] = array(
'visible' => array(
':input[name="repeat[value]"]' => ['checked' => TRUE],
),
'required' => [
':input[name="repeat[value]"]' => ['checked' => TRUE],
],
);
$form['title']['#states'] = array(
'visible' => array(
':input[name="transaction_category"]' => ['value' => 'new'],
),
'required' => [
':input[name="transaction_category"]' => ['value' => 'new'],
],
);
$form['transaction_category']['widget']['#ajax'] = [
'callback' => 'update_category_data',
'disable-refocus' => FALSE,
'event' => 'change',
];
$form['title']['#attributes']['class'][] = 'form-title-wrapper';
if($form_id == 'et_transaction_edit_form') {
if(isset($form['transaction_category']['widget']['#default_value'][0])) {
$transaction_category = $form['transaction_category']['widget']['#default_value'][0];
if($transaction_category != 'new') {
// $form['title']['#disabled'] =true;
}
}
}
// alter available options
$ids = \Drupal::entityQuery('et_transaction')
->accessCheck(TRUE)
->condition('parent_category','value', '0')
->execute();
$parent_category_options = array(
"_none" => "- None -"
);
foreach ($ids as $key => $entity_id) {
$EtTransaction = EtTransaction::load($entity_id);
$parent_category_options[$entity_id] = $EtTransaction->getTitle();
}
if ($form_state->getFormObject() instanceOf EntityFormInterface) {
$current_entity = $form_state->getformObject()->getEntity();
$current_eid = $current_entity->id();
unset($parent_category_options[$current_eid]);
$parent_transaction_id = $current_entity->parent_transaction_id->value;
if($parent_transaction_id > 0) {
$paernt_transaction = EtTransaction::load($parent_transaction_id);
if($paernt_transaction) {
$parent_title = $paernt_transaction->getTitle();
$form['repeat']['#access'] = false;
$form['repeat_every']['#access'] = false;
$form['end_date']['#access'] = false;
$form['specific_month_days']['#access'] = false;
$form['specific_week_days']['#access'] = false;
$form['transaction_category']['#access'] = false;
$form['category']['#access'] = false;
$form['title']['#access'] = false;
$form['date']['#access'] = false;
$form['transaction_type']['#access'] = false;
$form['uid']['#access'] = false;
$options = ['absolute' => TRUE];
$url = $paernt_transaction->toUrl('canonical', $options)->toString();
$link = "<a href = {$url}>{$parent_title}</a>";
$actions_prefix = "<strong>Note:</strong> <em>This is automatically created transaction. Please check for the parent transaction {$link}.</em>";
} else {
$actions_prefix = "<strong>Note:</strong> <em>This is automatically created transaction. The parent transaction has been removed or does not exist at the moment.</em>";
}
$form['actions']['#prefix'] = $actions_prefix;
}
}
$form['category']['widget']['#options'] = $parent_category_options;
}
}
/**
* @param $form
* @param \Drupal\Core\Form\FormStateInterface $form_state
* @return \Drupal\Core\Ajax\AjaxResponse
*/
function updateSubdomain(&$form, FormStateInterface $form_state) {
$transaction_category = $form_state->getValue('transaction_category');
$category = $form_state->getValue('category');
$entity_id = $category[0]['target_id']??'';
$getTitle = '';
if($entity_id) {
$EtTransaction = EtTransaction::load($entity_id);
$getTitle = $EtTransaction->getTitle();
}
$response = new AjaxResponse();
$selector = 'input[name="title[0][value]"]';
$wrapper_selector = '.form-title-wrapper .js-form-item';
$title = $form_state->getValue('title');
$title = $title['0']['value'];
$title = trim($title);
$new_title = '#New Transaction';
if($title != '') {
$new_title = $title;
}
$response->addCommand(new InvokeCommand($selector, 'val', [$new_title]));
return $response;
}
/**
* @param $form
* @param \Drupal\Core\Form\FormStateInterface $form_state
* @return \Drupal\Core\Ajax\AjaxResponse
*/
function update_category_data(&$form, FormStateInterface $form_state) {
$transaction_category = $form_state->getValue('transaction_category');
$disabled = false;
if(isset($transaction_category['0']['value']) && $transaction_category['0']['value'] == 'existing') {
$category = $form_state->getValue('category');
if(isset($category[0]['target_id'])) {
$disabled = true;
}
}
$response = new AjaxResponse();
$wrapper_selector = '.form-title-wrapper .js-form-item';
$selector = 'input[name="title[0][value]"]';
$title = $form_state->getValue('title');
$title = $title['0']['value'];
$title = trim($title);
$new_title = '#New Transaction';
if($title != '') {
$new_title = $title;
}
$response->addCommand(new InvokeCommand($selector, 'val', [$new_title]));
return $response;
}
/**
* Implements hook_form_views_exposed_form_alter()
*/
function expense_tracker_form_views_exposed_form_alter(&$form, FormStateInterface $form_state, $form_id)
{
$view = $form_state->get('view');
switch ($view->id()) {
case 'expense_tracker_admin':
$form['title']['#size'] = '25';
$form['uid']['#size'] = '30';
$date_format = 'Y-m-d';
if(isset($form['from'])) {
$form['from']['#type'] = 'date';
$form['from_date_format']['#type'] = $date_format;
$form['from_date_format']['#size'] = 15;
}
if(isset($form['to'])) {
$form['to']['#type'] = 'date';
$form['to_date_format']['#type'] = $date_format;
$form['to_date_format']['#size'] = 15;
}
break;
}
}
/**
* Repeat transactions according to the selected options
* @return [type]
*/
function repeat_transactions() {
repeat_transactions_every('day');
repeat_transactions_every('week');
repeat_transactions_every('month');
repeat_transactions_every('year');
repeat_transactions_every('working_days');
repeat_transactions_every('specific_week_days');
repeat_transactions_every('specific_month_days');
}
/**
* Create duplicate transactions if the repeat is set
* to 1 day
* @return
*/
function repeat_transactions_every($repeat_type) {
$current_time = time();
$one_day_seconds = 86400;
$day_hour = 24;
$repeat_every = false;
switch ($repeat_type) {
case 'day':
$day_ago = strtotime('-1 day');
break;
case 'week':
$seven_day_seconds = $one_day_seconds*7;
$day_ago = strtotime('-1 week');
$day_hour = $day_hour*7;
break;
case 'month':
$one_month_seconds = $one_day_seconds*30;
$day_ago = strtotime('-1 month');
$day_hour = $day_hour*30;
break;
case 'working_days':
case 'specific_week_days':
case 'specific_month_days':
$day_ago = strtotime('-1 day');
$repeat_every = true;
break;
case 'year':
$one_year_seconds = 31556926;
$day_ago = strtotime('-1 year');
$day_hour = $day_hour*365;
break;
}
// create duplicate for repeat one day
$ids = \Drupal::entityQuery('et_transaction')
->accessCheck(TRUE)
->condition('parent_transaction_id', '1', '<')
->condition('repeat', 1)
->condition('repeat_every', $repeat_type)
->condition('last_transaction_time', $day_ago,'<')
->condition('end_date', $current_time,'>')
->execute();
if(!empty($ids)) {
foreach ($ids as $key => $entity_id) {
$valid = false;
$EtTransaction = EtTransaction::load($entity_id);
if($repeat_every) {
$repeat_every = $EtTransaction->repeat_every->value;
switch ($repeat_every) {
case 'working_days':
$valid_days = array(
'1',
'2',
'3',
'4',
'5'
);
$today = date("N");
if(in_array($today, $valid_days)) {
$valid = true;
}
break;
case 'specific_week_days':
$today_date = date("N");
$specific_week_days = $EtTransaction->specific_week_days->getValue();
$idss = array_column($specific_week_days, 'value');
if(in_array($today_date, $idss)) {
$valid = true;
}
break;
case 'specific_month_days':
$today_date = date("j");
$specific_month_days = $EtTransaction->specific_month_days->getValue();
$idss = array_column($specific_month_days, 'value');
if(in_array($today_date, $idss)) {
$valid = true;
}
break;
}
}
if(!$valid) {
// check if the current tine is to create entity for repeat
$last_transaction_time = $EtTransaction->last_transaction_time->value;
if($last_transaction_time == 0) {
$date = $EtTransaction->date->value;
$diff_hours = diffBtwTimesAsPerType($current_time, $date, $returnType=2);
} else {
$diff_hours = diffBtwTimesAsPerType($current_time, $last_transaction_time, $returnType=2);
}
if($diff_hours > $day_hour) {
$valid = true;
}
}
if($valid) {
if (is_object($EtTransaction)) {
$clonednode = $EtTransaction->createDuplicate();
if($clonednode) {
$clonednode->set('parent_transaction_id', $entity_id);
$clonednode->set('date', $current_time);
$clonednode->set('transaction_category', 'existing');
$clonednode->set('created_type', 'automatic');
$clonednode->save();
$EtTransaction->last_transaction_time->value = time();
$EtTransaction->save();
}
}
}
}
}
}
function diffBtwTimesAsPerType($start, $end, $returnType=1) {
$seconds_diff = $start - $end;
if($returnType == 1){
return $seconds_diff/60;
//minutes
}else if($returnType == 2){
return $seconds_diff/3600;
//hours
}else{
return $seconds_diff/3600/24;
//days
}
}
/**
* Implements hook_views_query_alter().
*/
function expense_tracker_views_query_alter(ViewExecutable $view, QueryPluginBase $query) {
if ($view->id() == 'expense_tracker_admin' && !empty($view->exposed_raw_input['to']) && $view->exposed_raw_input['to'] !== 'all') {
foreach ($query->where as &$condition_group) {
foreach ($condition_group['conditions'] as &$condition) {
$check_string = 'et_transaction_field_data.date <=';
if (strpos($condition['field'], $check_string) !== false) {
$to = \Drupal::request()->get('to');
$date = new \DateTime($to);
$date->modify('+1 day');
$new_to = $date->format('Y-m-d');
$strtotime = strtotime($new_to);
$condition['field'] = $check_string.$strtotime;
}
}
}
}
if($view->id() == 'expense_tracker_admin') {
$current_user = \Drupal::currentUser();
if (!$current_user->hasPermission('access all expense_tracker')) {
$uid = $current_user->id();
$query->addWhere('view_access', 'et_transaction_field_data.uid', [$uid], 'IN');
}
}
}
/**
* get sum of all amounts between two timestamps
* @param [type] $start_timestamp
* @param [type] $end_timestamp
* @return [type]
*/
function get_total_amounts($report_parameters) {
$author = $report_parameters['author']??'';
$category = $report_parameters['category']??'';
$start_timestamp = $report_parameters['start_timestamp']??'';
$end_timestamp = $report_parameters['end_timestamp']??'';
$transaction_type = $report_parameters['report_type']??'';
$query = \Drupal::entityQuery('et_transaction')
->accessCheck(TRUE)
->condition('date', $start_timestamp, '>=')
->condition('date', $end_timestamp, '<=')
->sort('id', 'DESC');
if(in_array($transaction_type, array('income', 'expense'))) {
$query = $query->condition('transaction_type', $transaction_type);
}
if($author != '') {
$uid = get_substring_between($author, '(', ')');
$query = $query->condition('uid', $uid);
}
if($category != '') {
$category_id = get_substring_between($category, '(', ')');
$or = $query->orConditionGroup();
$or->condition('category', $category_id);
$or->condition('id', $category_id);
$query = $query->condition($or);
}
$results = $query->execute();
$entity_ids = array();
$total_amount = 0.0;
$results_data = array();
$transaction_details = array();
if(!empty($results)) {
$entity_ids = array_keys($results);
foreach ($entity_ids as $key => $entity_id) {
$EtTransaction = EtTransaction::load($entity_id);
$amount = $EtTransaction->getAmount();
$parent_category = $EtTransaction->getParentCategory();
if($parent_category) {
$entity_id = $parent_category->id();
if(isset($transaction_details[$entity_id]['amount'])) {
$amount = $amount + $transaction_details[$entity_id]['amount'];
}
}
$total_amount = $total_amount + $amount;
$transaction_details[$entity_id]['title'] = $EtTransaction->getTitle();
$transaction_details[$entity_id]['amount'] = $amount;
}
}
$results_data['total_amount'] = $total_amount;
$results_data['transaction_details'] = $transaction_details;
return $results_data;
}
/**
* Add a message before the customer view
* Implements template_preprocess_views_view().
*/
function expense_tracker_preprocess_views_view(&$vars) {
$view = $vars["view"];
if($view->storage->id() == 'expense_tracker_admin') {
$referrer = \Drupal::request()->get('referrer');
if($referrer == 'statements') {
$url = Url::fromRoute('expense_tracker.like_configurations');
$link = Link::fromTextAndUrl(t('<h5>Go back to Income Expense Statements page</h5>'), $url);
$link->getUrl()->setOption('attributes', ['class' => 'go-back-link']);
$vars['header'][] = $link->toRenderable();
}
}
}
/**
* Get substring between two strings
* @param [type] $string
* @param [type] $start
* @param [type] $end
* @return [type]
*/
function get_substring_between($string, $start, $end){
$string = ' ' . $string;
$ini = strpos($string, $start);
if ($ini == 0) return '';
$ini += strlen($start);
$len = strpos($string, $end, $ini) - $ini;
return substr($string, $ini, $len);
}
/**
* Generate mapped data
* @param [type] $contentData
* @param [type] $batch
* @param [type] $revision
* @return [type]
*/
function et_mapped_data($contentData, $batch, $import_to, $file_extension, $update_content) {
$item = array();
$row_headings = $contentData[0];
if($file_extension == 'xlsx') {
unset($contentData[0]);
}
foreach ($contentData as $key => $content) {
$item = array();
if($file_extension == 'xlsx') {
foreach ($content as $content_key => $content_value) {
$item[$row_headings[$content_key]] = $content_value;
}
} else {
$item = $content;
}
$records['data'] = $item;
$batch['operations'][] = [
'et_perform_batch_action', [$records, $import_to, $update_content]
];
}
return $batch;
}
/*
* Callback function once the process is finished.
*/
function et_batch_finished_callback($success, $results, $operations) {
if ($success) {
$results_data = (isset($results['results']))?$results['results']:[];
$count = count($results_data);
$message = \Drupal::translation()->formatPlural(
$count ,
'One record processed.', '@count records processed.'
);
\Drupal::messenger()->addMessage($message);
} else {
$message = t('Finished with an error.');
\Drupal::messenger()->addMessage($message, 'error');
}
}
/**
* Function to handle a batch process
*/
function et_perform_batch_action($results, $import_to, $update_content, &$context){
$title = '';
try {
$data = $results['data'];
switch ($import_to) {
case 'et_transaction':
$title = $data['title'];
if(trim($title) != '') {
$amount = $data['amount'];
$date = $data['date'];
$type = $data['type'];
if($date != '') {
$date_timestamp = strtotime($date);
} else {
$date_timestamp = time();
}
$query = \Drupal::entityQuery('et_transaction')
->accessCheck(TRUE)
->range(0,1)
->condition('title', $title)
->condition('transaction_category', 'new')
->condition('parent_category', '0');
$results = $query->execute();
// check if result is empty or not
if(empty($results)) {
// create book author if already not exists
$transaction = EtTransaction::create([
'title' =>$title,
'amount' => $amount,
'date' => $date_timestamp,
'transaction_type' => $type,
]);
$transaction->save();
} else if(!empty($results)) {
// if author already exists and the update content checkbox
// checked then update the author content
$entity_id = current($results);
$parent_transaction = EtTransaction::load($entity_id);
$parent_title = $parent_transaction->title->value;
$transaction = EtTransaction::create([
'title' =>$parent_title,
'amount' => $amount,
'date' => $date_timestamp,
'transaction_type' => $type,
'transaction_category' => 'existing',
'category' => $entity_id,
]);
$transaction->save();
}
}
break;
}
$context['results']['results'][] = $content_title;
$context['message'] = t('Updated @title', array('@title' => $content_title));
} catch (Exception $e) {
$message .= $e->getMessage();
dd($message);
$type = 'error';
log_updates($logger_type, $message, $type);
}
}
/**
* Print array for debuging
* @param [type] $array
* @param [type] $die
* @return [type]
*/
function etpr($array, $die) {
echo "<pre>";
print_r($array);
echo "</pre>";
if($die) {
die;
}
}
/**
* remove spacesa ad lowercase the string
* @param [type] $text
* @return [type]
*/
function clear_text_ettransaction($text)
{
$text = trim($text);
$text = preg_replace('!\s+!', '', $text);
return $text;
}
/**
* Implements hook_entity_presave
* @param Drupal\Core\Entity\EntityInterface $entity
* @return [type]
*/
function expense_tracker_entity_presave(EntityInterface $entity) {
if ($entity->getEntityType()->id() == 'et_transaction') {
if($entity->date->value == '') {
$entity->set('date', time());
}
if($entity->transaction_category->value == 'existing') {
$parent_category_id = $entity->parent_category->value;
if($parent_category_id) {
$parent_category = EtTransaction::load($parent_category_id);
if($parent_category) {
$parent_category_title = $parent_category->title->value;
$entity->set('title', $parent_category_title);
}
}
}
}
}