Sindbad~EG File Manager
<?php
/**
* @package RSDirectory!
* @copyright (c) 2013 - 2021 RSJoomla!
* @link https://www.rsjoomla.com
* @license GNU General Public License http://www.gnu.org/licenses/gpl-3.0.en.html
*/
defined('_JEXEC') or die('Restricted access');
// Load the RSDirectory! Plugin helper class file if it exists or stop the execution of the script if it doesn't exist.
if ( !file_exists(JPATH_ADMINISTRATOR . '/components/com_rsdirectory/helpers/plugin.php') )
return;
require_once JPATH_ADMINISTRATOR . '/components/com_rsdirectory/helpers/plugin.php';
use Joomla\CMS\Factory;
/**
* RSDirectory! Plugin.
*/
class plgSystemRSDirectory extends RSDirectoryPlugin
{
/**
* Class constructor.
*
* @access public
*
* @param object &$subject
* @param array $config
*/
private $renew_types = 0;
public function __construct(&$subject, $config)
{
parent::__construct($subject, $config);
if ( file_exists(JPATH_ADMINISTRATOR . '/components/com_rsdirectory/helpers/rsdirectory.php') )
{
require_once JPATH_ADMINISTRATOR . '/components/com_rsdirectory/helpers/rsdirectory.php';
}
}
/**
* On after initialise.
*
* @access public
*/
public function onAfterDispatch()
{
// Exit the function if the RSDirectory! helper class is not loaded.
if ( !class_exists('RSDirectoryHelper') )
return;
$this->renew_types = (int) RSDirectoryConfig::getInstance()->get('renew_types', 0);
$this->checkPromotedEntries();
$this->renewEntries();
$this->denyClaims();
$this->sendOnEntriesExpiration();
$this->sendEntriesExpirationNotifications();
$this->sendEntriesExpirationNotificationsRenew();
}
/**
* Method to unpromote entries if the promoted expiry datetime has passed.
*
* @access private
*/
private function checkPromotedEntries() {
$last_entries_promoted_check = RSDirectoryConfig::getInstance()->get('last_entries_promoted_check');
$entries_promoted_check_interval = RSDirectoryConfig::getInstance()->get('entries_promoted_check_interval');
// Exit the function if the renewal interval did not pass yet.
if ( $last_entries_promoted_check + $entries_promoted_check_interval > Factory::getDate()->toUnix () )
return;
// Get DBO
$db = Factory::getDbo();
// Update the last entries promoted check time.
$option = (object)array(
'name' => 'last_entries_promoted_check',
'value' => Factory::getDate()->toUnix (),
);
$db->updateObject('#__rsdirectory_config', $option, 'name');
// Update promoted_expiry and promoted field.
$query = $db->getQuery(true)
->update( $db->qn('#__rsdirectory_entries') )
->set( $db->qn('promoted_expiry') . ' = ' . $db->q( '0000-00-00 00:00:00' ) )
->set( $db->qn('promoted') . ' = ' . $db->q( 0 ) )
->where( $db->qn('promoted_expiry') . ' < ' . $db->q(Factory::getDate()->toSql()))
->where( $db->qn('promoted_expiry') . ' != ' . $db->q('0000-00-00 00:00:00'))
->where( $db->qn('promoted') . ' = ' . $db->q( 1 ));
$db->setQuery($query);
$db->execute();
}
/**
* Method to renew entries.
*
* @access private
*/
private function renewEntries()
{
$last_entries_renewals_time = RSDirectoryConfig::getInstance()->get('last_entries_renewals_time');
$entries_renewals_interval = RSDirectoryConfig::getInstance()->get('entries_renewals_interval');
// Exit the function if the renewal interval did not pass yet.
if ( $last_entries_renewals_time + $entries_renewals_interval > Factory::getDate()->toUnix () )
return;
// Get DBO.
$db = Factory::getDbo();
// Update the last entries renewals time.
$option = (object)array(
'name' => 'last_entries_renewals_time',
'value' => Factory::getDate()->toUnix (),
);
$db->updateObject('#__rsdirectory_config', $option, 'name');
$select = array(
$db->qn('e') . '.*',
$db->qn('ec') . '.*',
$db->qn('c.title', 'category_title'),
$db->qn('c.path', 'category_path'),
);
$query = $db->getQuery(true)
->select($select)
->from( $db->qn('#__rsdirectory_entries', 'e') )
->innerJoin( $db->qn('#__rsdirectory_entries_custom', 'ec') . ' ON ' . $db->qn('e.id') . ' = ' . $db->qn('ec.entry_id') )
->innerJoin( $db->qn('#__categories', 'c') . ' ON ' . $db->qn('e.category_id') . ' = ' . $db->qn('c.id') )
->where( $db->qn('e.published') . ' = ' . $db->q(1) )
->where( $db->qn('e.renew') . ' = ' . $db->q(1) )
->where( $db->qn('e.expiry_time') . ' != ' . $db->q('0000-00-00 00:00:00') )
->where( $db->qn('e.expiry_time') . ' < ' . $db->q( Factory::getDate()->toSql() ) )
->where( $db->qn('e.user_id') . ' NOT IN (SELECT '.$db->qn('u.id').' FROM ' . $db->qn('#__users', 'u') .' WHERE '.$db->qn('u.block').' != '.$db->q(0).')');
$db->setQuery($query, 0, 10);
$entries = $db->loadObjectList();
// Exit the function if there are no entries to be renewed.
if (!$entries)
return;
// Load language.
$lang = Factory::getLanguage();
$lang->load('com_rsdirectory', JPATH_SITE);
// Process each entry.
foreach ($entries as $i => $entry)
{
// check if user exists
$is_user = $this->checkUser($entry->user_id);
if (is_null($is_user))
{
unset($entries[$i]);
continue;
}
$entry_credits_objects = $this->getEntryCreditsObject($entry);
// check if the user has sufficient credits
$total_credits = 0;
foreach ($entry_credits_objects as $object) {
$total_credits += $object->credits;
}
$checkCredits = RSDirectoryCredits::checkUserCredits($total_credits, $entry->user_id);
if (!$checkCredits)
continue;
// Skip this entry if the user was not successfully charged.
if ( !RSDirectoryCredits::addEntryCreditsObjects($entry->id, $entry_credits_objects, $entry->user_id) )
continue;
$expiry_time = Factory::getDate();
$expiry_time->modify("+$entry->period days");
// Update expiry time.
$entry_data = (object)array(
'id' => $entry->id,
'published_time' => Factory::getDate()->toSql(),
'expiry_time' => $expiry_time->toSql(),
);
// Update reference
$entry->published_time = $entry_data->published_time;
$entry->expiry_time = $entry_data->expiry_time;
$db->updateObject('#__rsdirectory_entries', $entry_data, 'id');
}
// Regenerate entries titles.
if (!empty($entries))
{
RSDirectoryHelper::regenerateEntriesTitles($entries);
}
}
protected function checkUser($user_id)
{
static $users = array();
if (!isset($users[$user_id]))
{
// Get DBO.
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select($db->qn('id'))
->from($db->qn('#__users'))
->where($db->qn('id') . ' = ' . $db->q($user_id) . '');
$db->setQuery($query);
$users[$user_id] = $db->loadResult();
}
return $users[$user_id];
}
/**
* Method to send notifications after the entries expired.
*
* @access private
*/
private function sendOnEntriesExpiration()
{
// Check the last time this has been run
$date = Factory::getDate();
$now = $date->toUnix();
$config = RSDirectoryConfig::getInstance();
if ($now <$config->get('expire_last_run') + $config->get('expire_check_in', 5)*60)
return;
// update config value for last time the expiration emails were sent
$config->set('expire_last_run', $now);
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('*')
->from( $db->qn('#__rsdirectory_email_messages') )
->where( $db->qn('type') . ' = ' . $db->q('entry_expiration') )
->where( $db->qn('published') . ' = ' . $db->q(1) );
$db->setQuery($query);
$emails = $db->loadObjectList();
if (!$emails)
return;
require_once JPATH_ADMINISTRATOR . '/components/com_rsdirectory/helpers/email.php';
foreach ($emails as $email)
{
$query = $db->getQuery(true)
->select( $db->qn('e') . '.*' )
->from( $db->qn('#__rsdirectory_entries', 'e') )
->where( $db->qn('e.published') . ' = ' . $db->q(1) )
->where( $db->qn('e.expiry_time') . ' != ' . $db->q('0000-00-00 00:00:00') )
->where( $db->qn('e.expiry_time') . ' < ' . $db->q( Factory::getDate()->toSql() ) )
->where( $db->qn('e.expiration_notice_time') . ' < ' . $db->qn('e.expiry_time') )
->where( $db->qn('e.user_id') . ' NOT IN (SELECT '.$db->qn('u.id').' FROM ' . $db->qn('#__users', 'u') .' WHERE '.$db->qn('u.block').' != '.$db->q(0).')');
if ($email->category_id)
{
$query->where( $db->qn('e.category_id') . ' = ' . $db->q($email->category_id) );
}
$db->setQuery($query, 0 , $config->get('notification_emails', 10));
$entries = $db->loadObjectList();
if (!$entries)
{
continue;
}
$entries = RSDirectoryHelper::getEntriesData($entries, true);
// check that entries is not null or empty
if (empty($entries))
{
continue;
}
foreach ($entries as $entry)
{
RSDirectoryEmail::getInstance('entry_expiration', $entry->form->fields, $entry, $entry->form)->send();
// Update expiration_notice_time.
$query = $db->getQuery(true)
->update( $db->qn('#__rsdirectory_entries') )
->set( $db->qn('expiration_notice_time') . ' = ' . $db->q( Factory::getDate()->toSql() ) )
->where( $db->qn('id') . ' = ' .$db->q($entry->id) . '' );
$db->setQuery($query);
$db->execute();
}
}
}
/**
* Method to send notifications x hours before the entries expire.
*
* @access private
*/
private function sendEntriesExpirationNotifications()
{
// Check the last time this has been run
$date = Factory::getDate();
$now = $date->toUnix();
$config = RSDirectoryConfig::getInstance();
if ($now < $config->get('expire_notification_last_run') + $config->get('expire_notification_check_in', 5)*60)
return;
// update config value for last time the expiration emails were sent
$config->set('expire_notification_last_run', $now);
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('*')
->from( $db->qn('#__rsdirectory_email_messages') )
->where( $db->qn('type') . ' = ' . $db->q('entry_expiration_notice') )
->where( $db->qn('published') . ' = ' . $db->q(1) )
->order( $db->qn('entry_expiration_period') . ' ASC' );
$db->setQuery($query);
$emails = $db->loadObjectList();
if (!$emails)
return;
require_once JPATH_ADMINISTRATOR . '/components/com_rsdirectory/helpers/email.php';
foreach ($emails as $email)
{
$expiry_time = Factory::getDate();
$expiry_time->modify("+$email->entry_expiration_period hours");
$query = $db->getQuery(true)
->select( $db->qn('e') . '.*' )
->from( $db->qn('#__rsdirectory_entries', 'e') )
->where( $db->qn('e.published') . ' = ' . $db->q(1) )
->where( $db->qn('e.expiry_time') . ' != ' . $db->q('0000-00-00 00:00:00') )
->where( $db->qn('e.expiry_time') . ' <= ' . $db->q( $expiry_time->toSql() ) )
->where( $db->qn('e.expiration_notice_time') . " < DATE_SUB( " . $db->qn('e.expiry_time') . ", INTERVAL $email->entry_expiration_period HOUR )" )
->where( $db->qn('e.renew') . ' = ' . $db->q(0) )
->where( $db->qn('e.user_id') . ' NOT IN (SELECT '.$db->qn('u.id').' FROM ' . $db->qn('#__users', 'u') .' WHERE '.$db->qn('u.block').' != '.$db->q(0).')');
if ($email->category_id)
{
$query->where( $db->qn('e.category_id') . ' = ' . $db->q($email->category_id) );
}
$db->setQuery($query, 0 , $config->get('notification_emails', 10));
$entries = $db->loadObjectList();
if (!$entries)
{
continue;
}
$entries = RSDirectoryHelper::getEntriesData($entries, true);
// check that entries is not null or empty
if (empty($entries))
{
continue;
}
foreach ($entries as $entry)
{
RSDirectoryEmail::getInstance($email, $entry->form->fields, $entry, $entry->form)->send();
// Update expiration_notice_time.
$query = $db->getQuery(true)
->update( $db->qn('#__rsdirectory_entries') )
->set( $db->qn('expiration_notice_time') . ' = ' . $db->q( Factory::getDate()->toSql() ) )
->where( $db->qn('id') . ' = ' .$db->q($entry->id) . '' );
$db->setQuery($query);
$db->execute();
}
}
}
private function sendEntriesExpirationNotificationsRenew()
{
// Check the last time this has been run
$date = Factory::getDate();
$now = $date->toUnix();
$config = RSDirectoryConfig::getInstance();
if ($now < $config->get('expire_notification_renew_last_run') + $config->get('expire_notification_renew_check_in', 5)*60)
return;
// update config value for last time the expiration emails were sent
$config->set('expire_notification_renew_last_run', $now);
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('*')
->from( $db->qn('#__rsdirectory_email_messages') )
->where( $db->qn('type') . ' = ' . $db->q('entry_expiration_renew') )
->where( $db->qn('published') . ' = ' . $db->q(1) )
->order( $db->qn('entry_expiration_period') . ' ASC' );
$db->setQuery($query);
$emails = $db->loadObjectList();
if (!$emails)
return;
require_once JPATH_ADMINISTRATOR . '/components/com_rsdirectory/helpers/email.php';
// Load language files because we may need them
Factory::getLanguage()->load('com_rsdirectory', JPATH_SITE);
foreach ($emails as $email)
{
$expiry_time = Factory::getDate();
$expiry_time->modify("+$email->entry_expiration_period hours");
$select = array(
$db->qn('e') . '.*'
);
$query = $db->getQuery(true)
->select( $select )
->from( $db->qn('#__rsdirectory_entries', 'e') )
->where( $db->qn('e.published') . ' = ' . $db->q(1) )
->where( $db->qn('e.expiry_time') . ' != ' . $db->q('0000-00-00 00:00:00') )
->where( $db->qn('e.expiry_time') . ' <= ' . $db->q( $expiry_time->toSql() ) )
->where( $db->qn('e.expiration_notice_time') . " < DATE_SUB( " . $db->qn('e.expiry_time') . ", INTERVAL $email->entry_expiration_period HOUR )" )
->where( $db->qn('e.renew') . ' = ' . $db->q(1) )
->where( $db->qn('e.user_id') . ' NOT IN (SELECT '.$db->qn('u.id').' FROM ' . $db->qn('#__users', 'u') .' WHERE '.$db->qn('u.block').' != '.$db->q(0).')');
if ($email->category_id)
{
$query->where( $db->qn('e.category_id') . ' = ' . $db->q($email->category_id) );
}
$db->setQuery($query, 0 , $config->get('notification_emails', 10));
$entries = $db->loadObjectList();
if (!$entries)
{
continue;
}
$entries = RSDirectoryHelper::getEntriesData($entries, true);
// check that entries is not null or empty
if (empty($entries))
{
continue;
}
foreach ($entries as $entry)
{
$entry_credits_objects = $this->getEntryCreditsObject($entry);
// check if the user has the necessary credits
$total_credits = 0;
foreach ($entry_credits_objects as $object) {
$total_credits += $object->credits;
}
$checkCredits = RSDirectoryCredits::checkUserCredits($total_credits, $entry->user_id);
if (!$checkCredits) {
RSDirectoryEmail::getInstance($email, $entry->form->fields, $entry, $entry->form)->send();
// Update expiration_notice_time.
$query = $db->getQuery(true)
->update($db->qn('#__rsdirectory_entries'))
->set($db->qn('expiration_notice_time') . ' = ' . $db->q(Factory::getDate()->toSql()))
->where( $db->qn('id') . ' = ' .$db->q($entry->id) . '' );
$db->setQuery($query);
$db->execute();
}
}
}
}
private function getEntryCreditsObject($entry) {
static $credits = array();
if (!isset($credits[$entry->id])) {
$entry_credits_objects = array(
(object)array(
'object_type' => 'renewal',
'object_id' => 0,
'credits' => ($this->renew_types < 2 ? $entry->period_credits : '0'),
),
);
if ($this->renew_types > 0) {
// Get the form associated to the category.
$form = RSDirectoryHelper::getCategoryInheritedForm($entry->category_id);
// Get all the form fields.
$form_fields = RSDirectoryHelper::getFormFields($form->id);
foreach ($form_fields as $form_field)
{
// Get the field properties.
$properties = empty($form_field->properties) ? new JRegistry : $form_field->properties;
if ( in_array( $form_field->field_type, array('big_subtitle', 'small_subtitle', 'description', 'price', 'promoted') ) && ((empty($form_field->required) && !empty($entry->{$form_field->field_type}) || (!empty($form_field->required)))))
{
$entry_credits_objects[] = (object)array(
'object_type' => 'form_field',
'object_id' => $form_field->id,
'credits' => $properties->get('credits')
);
}
if ($form_field->field_type != 'map' && (!isset($entry->{'f_'.$form_field->id}) || (isset($entry->{'f_'.$form_field->id}) && !strlen($entry->{'f_'.$form_field->id})))) {
continue;
} else {
if ($form_field->field_type == 'map' && (strlen($entry->{'f_'.$form_field->id.'_address'}) || (strlen($entry->{'f_'.$form_field->id.'_lat'}) && strlen($entry->{'f_'.$form_field->id.'_lng'})))) {
$entry_credits_objects[] = (object)array(
'object_type' => 'form_field',
'object_id' => $form_field->id,
'credits' => $properties->get('credits')
);
continue;
} else if (strlen($entry->{'f_'.$form_field->id})) {
// handle credits for the multiple credits fields
if (in_array($form_field->field_type, array('dropdown', 'country', 'checkboxgroup', 'radiogroup'))) {
$selected_value = $entry->{'f_'.$form_field->id};
if ($form_field->field_type == 'checkboxgroup' || $properties->get('multiple')) {
$selected_value = explode("\r\n", $selected_value);
}
// get the items
$items = RSDirectoryHelper::getOptions( $form_field->properties->get('items') );
$grouped_options = RSDirectoryHelper::getGroupedListOptions($items);
// The default credits set up for this field
$default_credits = $properties->get('credits');
// hold the custom credits found
$custom_credits_found = array();
// hold the normal credits
$normal_credits_found = 0;
if (isset($grouped_options['credits'])) {
if (is_array($selected_value)) {
foreach ($selected_value as $s_value) {
if (isset($grouped_options['credits'][$s_value])) {
$custom_credits_found[] = (int)$grouped_options['credits'][$s_value];
} else {
if ($default_credits && !$normal_credits_found) {
$normal_credits_found = $default_credits;
}
}
}
} else {
if (isset($grouped_options['credits'][$selected_value])) {
$custom_credits_found[] = (int)$grouped_options['credits'][$selected_value];
} else {
$normal_credits_found = $default_credits;
}
}
} else {
$normal_credits_found = $default_credits;
}
$all_credits = $normal_credits_found;
if (!empty($custom_credits_found)) {
$custom_credits_found = array_sum($custom_credits_found);
$all_credits += $custom_credits_found;
}
$entry_credits_objects[] = (object)array(
'object_type' => 'form_field',
'object_id' => $form_field->id,
'credits' => $all_credits
);
}
else
{
$entry_credits_objects[] = (object)array(
'object_type' => 'form_field',
'object_id' => $form_field->id,
'credits' => $properties->get('credits')
);
}
continue;
}
}
}
// files
$files = RSDirectoryHelper::getFilesObjectList(0, $entry->id);
if ($files)
{
foreach ($files as $file)
{
// Get the form field.
$form_field = RSDirectoryHelper::findFormField( array('id' => $file->field_id), $form_fields );
// In case the form fields that have files have been changed or do not exist anymore skip this part
if (!is_null($form_field))
{
$entry_credits_objects[] = (object) array(
'object_type' => 'uploaded_file',
'object_id' => $form_field->id,
'credits' => $form_field->properties->get('credits_per_file'),
);
$entry_credits_objects[] = (object) array(
'object_type' => 'form_field',
'object_id' => $form_field->id,
'credits' => $form_field->properties->get('credits'),
);
}
}
}
// the first element is always the renewall and it could be '0'
if (count($entry_credits_objects) > 1) {
// Store the publishing period credits just in case the cleaning returns an empty array
$tmp_entry_credits_object = $entry_credits_objects[0];
// Clean the entry credits objects array.
$entry_credits_objects = RSDirectoryCredits::cleanEntryCredits($entry_credits_objects);
// Use the temp credits object (this scenario is happening when all the fields have the credits set to "0", including the publishing period)
if (empty($entry_credits_objects)) {
$entry_credits_objects[] = $tmp_entry_credits_object;
}
}
}
$credits[$entry->id] = $entry_credits_objects;
}
return $credits[$entry->id];
}
private function denyClaims()
{
$date = Factory::getDate();
$now = $date->toUnix();
$config = RSDirectoryConfig::getInstance();
if ($now <$config->get('claim_last_run') + $config->get('claim_check_in', 5)*60)
return;
// update config value for last time the deny claims were set
$config->set('claim_last_run', $now);
$config = RSDirectoryConfig::getInstance();
$days = (int) $config->get('claim_days_auto_deny', 7);
if ($days > 0)
{
$expiry_time = Factory::getDate();
$expiry_time->modify("-$days days");
// Get DBO.
$db = Factory::getDbo();
// Get entry author display info (name, username, email..).
$author = RSDirectoryConfig::getInstance()->get('entry_author', 'name');
$select = array(
$db->qn('c') . '.*',
$db->qn('e.title'),
$db->qn('e.period'),
$db->qn('u.id', 'entry_author_id'),
$db->qn("u.$author", 'entry_author'),
$db->qn("cu.$author", 'claim_to_user_author'),
$db->qn("cu.email", 'claim_to_user_email'),
);
$query = $db->getQuery(true)
->select($select)
->from( $db->qn('#__rsdirectory_claims', 'c') )
->leftJoin( $db->qn('#__rsdirectory_entries', 'e') . ' ON ' . $db->qn('c.entry_id') . ' = ' . $db->qn('e.id') )
->leftJoin( $db->qn('#__users', 'u') . ' ON ' . $db->qn('c.from_user_id') . ' = ' . $db->qn('u.id') )
->leftJoin( $db->qn('#__users', 'cu') . ' ON ' . $db->qn('c.to_user_id') . ' = ' . $db->qn('cu.id') )
->where( $db->qn('c.date_claimed') . ' <= ' . $db->q($expiry_time->toSql()) )
->where( $db->qn('c.approved') . ' = ' . $db->q(0) )
->where( $db->qn('c.denied') . ' = ' . $db->q(0) );
$db->setQuery($query, 0, 10); // 10 claims per cycle
$claims = $db->loadObjectList();
if (!$claims)
{
return;
}
foreach ($claims as $claim)
{
// refund the credits spent
if ($claim->claim_credits && $claim->paid) {
$claim->claim_credits = (int) $claim->claim_credits;
$entry_credits_objects = array(
(object)array(
'object_type' => 'claim_entry_refund',
'object_id' => 0,
'credits' => -$claim->claim_credits,
),
);
$entry_credits_objects = RSDirectoryCredits::cleanEntryCredits($entry_credits_objects);
if ( !RSDirectoryCredits::addEntryCreditsObjects($claim->entry_id, $entry_credits_objects, $claim->to_user_id) )
{
continue;
}
}
// mark the request as denied
// change the data for approval to the claimed table
// Update expiration_notice_time.
$query = $db->getQuery(true)
->update( $db->qn('#__rsdirectory_claims') )
->set( $db->qn('denied') . ' = ' . $db->q( 1 ) )
->set( $db->qn('denied_date') . ' = ' . $db->q( Factory::getDate()->toSql() ) )
->where( $db->qn('id') . ' = ' .$db->q($claim->id) );
$db->setQuery($query);
if ($db->execute())
{
// send the email
require_once JPATH_ADMINISTRATOR . '/components/com_rsdirectory/helpers/email.php';
$entry = RSDirectoryHelper::getEntry($claim->entry_id);
$form_fields = RSDirectoryHelper::getFormFields($entry->form_id);
$form = RSDirectoryHelper::getCategoryInheritedForm($entry->category_id);
$custom_fields = array(
'claim.name' => $claim->name,
'claim.email' => $claim->email,
'claim.phone_number' => $claim->phone_number
);
RSDirectoryEmail::getInstance('deny_claim', $form_fields, $entry, $form, $custom_fields)->send();
}
}
}
}
}
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists