/** * Download logic * * @access public * @since 1.0 */ function download() { // Import and Initialize some joomla API variables jimport('joomla.filesystem.file'); $app = JFactory::getApplication(); $db = JFactory::getDBO(); $user = JFactory::getUser(); $task = JRequest::getVar('task', 'download'); $session = JFactory::getSession(); $method = JRequest::getVar('method', 'download'); if ($method != 'view' && $method != 'download') { die('unknown download method:' . $method); } // ******************************************************************************************************************* // Single file download (via HTTP request) or multi-file downloaded (via a folder structure in session or in DB table) // ******************************************************************************************************************* if ($task == 'download_tree') { // TODO: maybe move this part in module $cart_id = JRequest::getVar('cart_id', 0); if (!$cart_id) { // Get zTree data and parse JSON string $tree_var = JRequest::getVar('tree_var', ""); if ($session->has($tree_var, 'flexicontent')) { $ztree_nodes_json = $session->get($tree_var, false, 'flexicontent'); } $nodes = json_decode($ztree_nodes_json); } else { $cart_token = JRequest::getVar('cart_token', ''); $query = ' SELECT * FROM #__flexicontent_downloads_cart WHERE id=' . $cart_id; $db->setQuery($query); $cart = $db->loadObject(); if ($db->getErrorNum()) { JFactory::getApplication()->enqueueMessage(__FUNCTION__ . '(): SQL QUERY ERROR:<br/>' . nl2br($db->getErrorMsg()), 'error'); } if (!$cart) { echo JText::_('cart id no ' . $cart_id . ', was not found'); jexit(); } $cart_token_matches = $cart_token == $cart->token; // no access will be checked $nodes = json_decode($cart->json); } // Some validation check if (!is_array($nodes)) { $app->enqueueMessage("Tree structure is empty or invalid", 'notice'); $this->setRedirect('index.php', ''); return; } $app = JFactory::getApplication(); $tmp_ffname = 'fcmd_uid_' . $user->id . '_' . date('Y-m-d__H-i-s'); $targetpath = JPath::clean($app->getCfg('tmp_path') . DS . $tmp_ffname); $tree_files = $this->_traverseFileTree($nodes, $targetpath); //echo "<pre>"; print_r($tree_files); jexit(); if (empty($tree_files)) { $app->enqueueMessage("No files selected for download", 'notice'); $this->setRedirect('index.php', ''); return; } } else { $file_node = new stdClass(); $file_node->fieldid = JRequest::getInt('fid', 0); $file_node->contentid = JRequest::getInt('cid', 0); $file_node->fileid = JRequest::getInt('id', 0); $coupon_id = JRequest::getInt('conid', 0); $coupon_token = JRequest::getString('contok', ''); if ($coupon_id) { $_nowDate = 'UTC_TIMESTAMP()'; $_nullDate = $db->Quote($db->getNullDate()); $query = ' SELECT *' . ', CASE WHEN ' . ' expire_on = ' . $_nullDate . ' OR expire_on > ' . $_nowDate . ' THEN 0 ELSE 1 END AS has_expired' . ', CASE WHEN ' . ' hits_limit = -1 OR hits < hits_limit' . ' THEN 0 ELSE 1 END AS has_reached_limit' . ' FROM #__flexicontent_download_coupons' . ' WHERE id=' . $coupon_id . ' AND token=' . $db->Quote($coupon_token); $db->setQuery($query); $coupon = $db->loadObject(); if ($db->getErrorNum()) { echo __FUNCTION__ . '(): SQL QUERY ERROR:<br/>' . nl2br($db->getErrorMsg()); jexit(); } if ($coupon) { $slink_valid_coupon = !$coupon->has_reached_limit && !$coupon->has_expired; if (!$slink_valid_coupon) { $query = ' DELETE FROM #__flexicontent_download_coupons WHERE id=' . $coupon->id; $db->setQuery($query); $db->execute(); } } $file_node->coupon = !empty($coupon) ? $coupon : false; // NULL will not be catched by isset() } $tree_files = array($file_node); } // ************************************************** // Create and Execute SQL query to retrieve file info // ************************************************** // Create SELECT OR JOIN / AND clauses for checking Access $access_clauses['select'] = ''; $access_clauses['join'] = ''; $access_clauses['and'] = ''; $using_access = empty($cart_token_matches) && empty($slink_valid_coupon); if ($using_access) { // note CURRENTLY multi-download feature does not use coupons $access_clauses = $this->_createFieldItemAccessClause($get_select_access = true, $include_file = true); } // *************************** // Get file data for all files // *************************** $fields_props = array(); $fields_conf = array(); $valid_files = array(); $email_recipients = array(); foreach ($tree_files as $file_node) { // Get file variable shortcuts (reforce being int) $field_id = (int) $file_node->fieldid; $content_id = (int) $file_node->contentid; $file_id = (int) $file_node->fileid; if (!isset($fields_conf[$field_id])) { $q = 'SELECT attribs, name, field_type FROM #__flexicontent_fields WHERE id = ' . (int) $field_id; $db->setQuery($q); $fld = $db->loadObject(); $fields_conf[$field_id] = new JRegistry($fld->attribs); $fields_props[$field_id] = $fld; } $field_type = $fields_props[$field_id]->field_type; $query = 'SELECT f.id, f.filename, f.filename_original, f.altname, f.secure, f.url, f.hits' . ', i.title as item_title, i.introtext as item_introtext, i.fulltext as item_fulltext, u.email as item_owner_email' . ', i.access as item_access, i.language as item_language, ie.type_id as item_type_id' . ', CASE WHEN CHAR_LENGTH(i.alias) THEN CONCAT_WS(\':\', i.id, i.alias) ELSE i.id END as itemslug' . ', CASE WHEN CHAR_LENGTH(c.alias) THEN CONCAT_WS(\':\', c.id, c.alias) ELSE c.id END as catslug' . ', dh.id as history_id' . $access_clauses['select'] . ' FROM #__flexicontent_files AS f ' . ($field_type == 'file' ? ' LEFT JOIN #__flexicontent_fields_item_relations AS rel ON rel.field_id = ' . $field_id : '') . ' LEFT JOIN #__flexicontent_fields AS fi ON fi.id = ' . $field_id . ' LEFT JOIN #__content AS i ON i.id = ' . $content_id . ' LEFT JOIN #__categories AS c ON c.id = i.catid' . ' LEFT JOIN #__flexicontent_items_ext AS ie ON ie.item_id = i.id' . ' LEFT JOIN #__flexicontent_types AS ty ON ie.type_id = ty.id' . ' LEFT JOIN #__users AS u ON u.id = i.created_by' . ' LEFT JOIN #__flexicontent_download_history AS dh ON dh.file_id = f.id AND dh.user_id = ' . (int) $user->id . $access_clauses['join'] . ' WHERE i.id = ' . $content_id . ' AND fi.id = ' . $field_id . ' AND f.id = ' . $file_id . ' AND f.published= 1' . $access_clauses['and']; $db->setQuery($query); $file = $db->loadObject(); if ($db->getErrorNum()) { echo __FUNCTION__ . '(): SQL QUERY ERROR:<br/>' . nl2br($db->getErrorMsg()); jexit(); } //echo "<pre>". print_r($file, true) ."</pre>"; exit; // ************************************************************** // Check if file was found AND IF user has required Access Levels // ************************************************************** if (empty($file) || $using_access && (!$file->has_content_access || !$file->has_field_access || !$file->has_file_access)) { if (empty($file)) { $msg = JText::_('FLEXI_FDC_FAILED_TO_FIND_DATA'); // Failed to match DB data to the download URL data } else { $msg = JText::_('FLEXI_ALERTNOTAUTH'); if (!empty($file_node->coupon)) { if ($file_node->coupon->has_expired) { $msg .= JText::_('FLEXI_FDC_COUPON_HAS_EXPIRED'); } else { if ($file_node->coupon->has_reached_limit) { $msg .= JText::_('FLEXI_FDC_COUPON_REACHED_USAGE_LIMIT'); } else { $msg = "unreachable code in download coupon handling"; } } } else { if (isset($file_node->coupon)) { $msg .= "<br/> <small>" . JText::_('FLEXI_FDC_COUPON_NO_LONGER_USABLE') . "</small>"; } $msg .= '' . (!$file->has_content_access ? "<br/><br/> " . JText::_('FLEXI_FDC_NO_ACCESS_TO') . " -- " . JText::_('FLEXI_FDC_CONTENT_CONTAINS') . " " . JText::_('FLEXI_FDC_WEBLINK') . "<br/><small>(" . JText::_('FLEXI_FDC_CONTENT_EXPLANATION') . ")</small>" : '') . (!$file->has_field_access ? "<br/><br/> " . JText::_('FLEXI_FDC_NO_ACCESS_TO') . " -- " . JText::_('FLEXI_FDC_FIELD_CONTAINS') . " " . JText::_('FLEXI_FDC_WEBLINK') : '') . (!$file->has_file_access ? "<br/><br/> " . JText::_('FLEXI_FDC_NO_ACCESS_TO') . " -- " . JText::_('FLEXI_FDC_FILE') . " " : ''); } $msg .= "<br/><br/> " . JText::sprintf('FLEXI_FDC_FILE_DATA', $file_id, $content_id, $field_id); $app->enqueueMessage($msg, 'notice'); } // Only abort for single file download if ($task != 'download_tree') { $this->setRedirect('index.php', ''); return; } } // **************************************************** // (for non-URL) Create file path and check file exists // **************************************************** if (!$file->url) { $basePath = $file->secure ? COM_FLEXICONTENT_FILEPATH : COM_FLEXICONTENT_MEDIAPATH; $file->abspath = str_replace(DS, '/', JPath::clean($basePath . DS . $file->filename)); if (!JFile::exists($file->abspath)) { $msg = JText::_('FLEXI_REQUESTED_FILE_DOES_NOT_EXIST_ANYMORE'); $app->enqueueMessage($msg, 'notice'); // Only abort for single file download if ($task != 'download_tree') { $this->setRedirect('index.php', ''); return; } } } // ********************************************************************* // Increment hits counter of file, and hits counter of file-user history // ********************************************************************* $filetable = JTable::getInstance('flexicontent_files', ''); $filetable->hit($file_id); if (empty($file->history_id)) { $query = ' INSERT #__flexicontent_download_history ' . ' SET user_id = ' . (int) $user->id . ' , file_id = ' . $file_id . ' , last_hit_on = NOW()' . ' , hits = 1'; } else { $query = ' UPDATE #__flexicontent_download_history ' . ' SET last_hit_on = NOW()' . ' , hits = hits + 1' . ' WHERE id = ' . (int) $file->history_id; } $db->setQuery($query); $db->execute(); // ************************************************************************************************** // Increment hits on download coupon or delete the coupon if it has expired due to date or hits limit // ************************************************************************************************** if (!empty($file_node->coupon)) { if (!$file_node->coupon->has_reached_limit && !$file_node->coupon->has_expired) { $query = ' UPDATE #__flexicontent_download_coupons' . ' SET hits = hits + 1' . ' WHERE id=' . $file_node->coupon->id; $db->setQuery($query); $db->execute(); } } // ************************** // Special case file is a URL // ************************** if ($file->url) { // Check for empty URL $url = $file->filename_original ? $file->filename_original : $file->filename; if (empty($url)) { $msg = "File URL is empty: " . $file->url; $app->enqueueMessage($msg, 'error'); return false; } // skip url-based file if downloading multiple files if ($task == 'download_tree') { $msg = "Skipped URL based file: " . $url; $app->enqueueMessage($msg, 'notice'); continue; } // redirect to the file download link @header("Location: " . $url . ""); $app->close(); } // ********************************************************************* // Set file (tree) node and assign file into valid files for downloading // ********************************************************************* $file->node = $file_node; $valid_files[$file_id] = $file; $file->hits++; $per_downloads = $fields_conf[$field_id]->get('notifications_hits_step', 20); if ($fields_conf[$field_id]->get('send_notifications') && $file->hits % $per_downloads == 0) { // Calculate (once per file) some text used for notifications $file->__file_title__ = $file->altname && $file->altname != $file->filename ? $file->altname . ' [' . $file->filename . ']' : $file->filename; $item = new stdClass(); $item->access = $file->item_access; $item->type_id = $file->item_type_id; $item->language = $file->item_language; $file->__item_url__ = JRoute::_(FlexicontentHelperRoute::getItemRoute($file->itemslug, $file->catslug, 0, $item)); // Parse and identify language strings and then make language replacements $notification_tmpl = $fields_conf[$field_id]->get('notification_tmpl'); if (empty($notification_tmpl)) { $notification_tmpl = JText::_('FLEXI_HITS') . ": " . $file->hits; $notification_tmpl .= '%%FLEXI_FDN_FILE_NO%% __file_id__: "__file_title__" ' . "\n"; $notification_tmpl .= '%%FLEXI_FDN_FILE_IN_ITEM%% "__item_title__":' . "\n"; $notification_tmpl .= '__item_url__'; } $result = preg_match_all("/\\%\\%([^%]+)\\%\\%/", $notification_tmpl, $translate_matches); $translate_strings = $result ? $translate_matches[1] : array(); foreach ($translate_strings as $translate_string) { $notification_tmpl = str_replace('%%' . $translate_string . '%%', JText::_($translate_string), $notification_tmpl); } $file->notification_tmpl = $notification_tmpl; // Send to hard-coded email list $send_all_to_email = $fields_conf[$field_id]->get('send_all_to_email'); if ($send_all_to_email) { $emails = preg_split("/[\\s]*;[\\s]*/", $send_all_to_email); foreach ($emails as $email) { $email_recipients[$email][] = $file; } } // Send to item owner $send_to_current_item_owner = $fields_conf[$field_id]->get('send_to_current_item_owner'); if ($send_to_current_item_owner) { $email_recipients[$file->item_owner_email][] = $file; } // Send to email assigned to email field in same content item $send_to_email_field = (int) $fields_conf[$field_id]->get('send_to_email_field'); if ($send_to_email_field) { $q = 'SELECT value ' . ' FROM #__flexicontent_fields_item_relations ' . ' WHERE field_id = ' . $send_to_email_field . ' AND item_id=' . $content_id; $db->setQuery($q); $email_values = $db->loadColumn(); foreach ($email_values as $i => $email_value) { if (@unserialize($email_value) !== false || $email_value === 'b:0;') { $email_values[$i] = unserialize($email_value); } else { $email_values[$i] = array('addr' => $email_value, 'text' => ''); } $addr = @$email_values[$i]['addr']; if ($addr) { $email_recipients[$addr][] = $file; } } } } } //echo "<pre>". print_r($valid_files, true) ."</pre>"; //echo "<pre>". print_r($email_recipients, true) ."</pre>"; //sjexit(); if (!empty($email_recipients)) { ob_start(); $sendermail = $app->getCfg('mailfrom'); $sendermail = JMailHelper::cleanAddress($sendermail); $sendername = $app->getCfg('sitename'); $subject = JText::_('FLEXI_FDN_FILE_DOWNLOAD_REPORT'); $message_header = JText::_('FLEXI_FDN_FILE_DOWNLOAD_REPORT_BY') . ': ' . $user->name . ' [' . $user->username . ']'; // **************************************************** // Send email notifications about file being downloaded // **************************************************** // Personalized email per subscribers foreach ($email_recipients as $email_addr => $files_arr) { $to = JMailHelper::cleanAddress($email_addr); $_message = $message_header; foreach ($files_arr as $filedata) { $_mssg_file = $filedata->notification_tmpl; $_mssg_file = str_ireplace('__file_id__', $filedata->id, $_mssg_file); $_mssg_file = str_ireplace('__file_title__', $filedata->__file_title__, $_mssg_file); $_mssg_file = str_ireplace('__item_title__', $filedata->item_title, $_mssg_file); //$_mssg_file = str_ireplace('__item_title_linked__', $filedata->password, $_mssg_file); $_mssg_file = str_ireplace('__item_url__', $filedata->__item_url__, $_mssg_file); $count = 0; $_mssg_file = str_ireplace('__file_hits__', $filedata->hits, $_mssg_file, $count); if ($count == 0) { $_mssg_file = JText::_('FLEXI_HITS') . ": " . $file->hits . "\n" . $_mssg_file; } $_message .= "\n\n" . $_mssg_file; } //echo "<pre>". $_message ."</pre>"; $from = $sendermail; $fromname = $sendername; $recipient = array($to); $html_mode = false; $cc = null; $bcc = null; $attachment = null; $replyto = null; $replytoname = null; $send_result = FLEXI_J16GE ? JFactory::getMailer()->sendMail($from, $fromname, $recipient, $subject, $_message, $html_mode, $cc, $bcc, $attachment, $replyto, $replytoname) : JUtility::sendMail($from, $fromname, $recipient, $subject, $_message, $html_mode, $cc, $bcc, $attachment, $replyto, $replytoname); } ob_end_clean(); } // * Required for IE, otherwise Content-disposition is ignored if (ini_get('zlib.output_compression')) { ini_set('zlib.output_compression', 'Off'); } if ($task == 'download_tree') { // Create target (top level) folder JFolder::create($targetpath, 0755); // Copy Files foreach ($valid_files as $file) { JFile::copy($file->abspath, $file->node->targetpath); } // Create text/html file with ITEM title / descriptions // TODO replace this with a TEMPLATE file ... $desc_filename = $targetpath . DS . "_descriptions"; $handle_txt = fopen($desc_filename . ".txt", "w"); $handle_htm = fopen($desc_filename . ".htm", "w"); fprintf($handle_htm, ' <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-gb" lang="en-gb" dir="ltr" > <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> </head> <body> '); foreach ($valid_files as $file) { fprintf($handle_txt, "%s", $file->item_title . "\n\n"); fprintf($handle_txt, "%s", flexicontent_html::striptagsandcut($file->item_introtext) . "\n\n"); if (strlen($file->item_fulltext)) { fprintf($handle_txt, "%s", flexicontent_html::striptagsandcut($file->item_fulltext) . "\n\n"); } fprintf($handle_htm, "%s", "<h2>" . $file->item_title . "</h2>"); fprintf($handle_htm, "%s", "<blockquote>" . $file->item_introtext . "</blockquote><br/>"); if (strlen($file->item_fulltext)) { fprintf($handle_htm, "%s", "<blockquote>" . $file->item_fulltext . "</blockquote><br/>"); } fprintf($handle_htm, "<hr/><br/>"); } fclose($handle_txt); fclose($handle_htm); // Get file list recursively, and calculate archive filename $fileslist = JFolder::files($targetpath, '.', $recurse = true, $fullpath = true); $archivename = $tmp_ffname . '.zip'; $archivepath = JPath::clean($app->getCfg('tmp_path') . DS . $archivename); // ****************** // Create the archive // ****************** /*$app = JFactory::getApplication('administrator'); $files = array(); foreach ($fileslist as $i => $filename) { $files[$i]=array(); $files[$i]['name'] = preg_replace("%^(\\\|/)%", "", str_replace($targetpath, "", $filename) ); // STRIP PATH for filename inside zip $files[$i]['data'] = implode('', file($filename)); // READ contents into string, here we use full path $files[$i]['time'] = time(); } $packager = JArchive::getAdapter('zip'); if (!$packager->create($archivepath, $files)) { $msg = JText::_('FLEXI_OPERATION_FAILED'). ": compressed archive could not be created"; $app->enqueueMessage($msg, 'notice'); $this->setRedirect('index.php', ''); return; }*/ $za = new flexicontent_zip(); $res = $za->open($archivepath, ZipArchive::CREATE); if ($res !== true) { $msg = JText::_('FLEXI_OPERATION_FAILED') . ": compressed archive could not be created"; $app->enqueueMessage($msg, 'notice'); $this->setRedirect('index.php', ''); return; } $za->addDir($targetpath, ""); $za->close(); // ********************************* // Remove temporary folder structure // ********************************* if (!JFolder::delete($targetpath)) { $msg = "Temporary folder " . $targetpath . " could not be deleted"; $app->enqueueMessage($msg, 'notice'); } // Delete old files (they can not be deleted during download time ...) $tmp_path = JPath::clean($app->getCfg('tmp_path')); $matched_files = JFolder::files($tmp_path, 'fcmd_uid_.*', $recurse = false, $fullpath = true); foreach ($matched_files as $archive_file) { //echo "Seconds passed:". (time() - filemtime($tmp_folder)) ."<br>". "$filename was last modified: " . date ("F d Y H:i:s.", filemtime($tmp_folder)) . "<br>"; if (time() - filemtime($archive_file) > 3600) { JFile::delete($archive_file); } } // Delete old tmp folder (in case that the some archiving procedures were interrupted thus their tmp folder were not deleted) $matched_folders = JFolder::folders($tmp_path, 'fcmd_uid_.*', $recurse = false, $fullpath = true); foreach ($matched_folders as $tmp_folder) { //echo "Seconds passed:". (time() - filemtime($tmp_folder)) ."<br>". "$filename was last modified: " . date ("F d Y H:i:s.", filemtime($tmp_folder)) . "<br>"; JFolder::delete($tmp_folder); } $dlfile = new stdClass(); $dlfile->filename = 'cart_files_' . date('m-d-Y_H-i-s') . '.zip'; // a friendly name instead of $archivename $dlfile->abspath = $archivepath; } else { $dlfile = reset($valid_files); } // Get file filesize and extension $dlfile->size = filesize($dlfile->abspath); $dlfile->ext = strtolower(JFile::getExt($dlfile->filename)); // Set content type of file (that is an archive for multi-download) $ctypes = array("pdf" => "application/pdf", "exe" => "application/octet-stream", "rar" => "application/zip", "zip" => "application/zip", "txt" => "text/plain", "doc" => "application/msword", "xls" => "application/vnd.ms-excel", "ppt" => "application/vnd.ms-powerpoint", "gif" => "image/gif", "png" => "image/png", "jpeg" => "image/jpg", "jpg" => "image/jpg", "mp3" => "audio/mpeg"); $dlfile->ctype = isset($ctypes[$dlfile->ext]) ? $ctypes[$dlfile->ext] : "application/force-download"; // ***************************************** // Output an appropriate Content-Type header // ***************************************** header("Pragma: public"); // required header("Expires: 0"); header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); header("Cache-Control: private", false); // required for certain browsers header("Content-Type: " . $dlfile->ctype); //quotes to allow spaces in filenames $download_filename = strlen($dlfile->filename_original) ? $dlfile->filename_original : $dlfile->filename; if ($method == 'view') { header("Content-Disposition: inline; filename=\"" . $download_filename . "\";"); } else { header("Content-Disposition: attachment; filename=\"" . $download_filename . "\";"); } header("Content-Transfer-Encoding: binary"); header("Content-Length: " . $dlfile->size); // ******************************* // Finally read file and output it // ******************************* if (!FLEXIUtilities::funcIsDisabled('set_time_limit')) { @set_time_limit(0); } $chunksize = 1 * (1024 * 1024); // 1MB, highest possible for fread should be 8MB if (1 || $dlfile->size > $chunksize) { $handle = @fopen($dlfile->abspath, "rb"); while (!feof($handle)) { print @fread($handle, $chunksize); ob_flush(); flush(); } fclose($handle); } else { // This is good for small files, it will read an output the file into // memory and output it, it will cause a memory exhausted error on large files ob_clean(); flush(); readfile($dlfile->abspath); } // **************************************************** // In case of multi-download clear the session variable // **************************************************** //if ($task=='download_tree') $session->set($tree_var, false,'flexicontent'); // Done ... terminate execution $app->close(); }