public/main/inc/lib/link.lib.php
<?php
/* For licensing terms, see /license.txt */
use Chamilo\CoreBundle\Framework\Container;
use Chamilo\CourseBundle\Entity\CLink;
use Chamilo\CourseBundle\Entity\CLinkCategory;
use Chamilo\CoreBundle\Component\Utils\ActionIcon;
use Chamilo\CoreBundle\Component\Utils\ObjectIcon;
use Chamilo\CoreBundle\Component\Utils\StateIcon;
/**
* Function library for the links tool.
*
* This is a complete remake of the original link tool.
* New features:
* - Organize links into categories;
* - favorites/bookmarks interface;
* - move links up/down within a category;
* - move categories up/down;
* - expand/collapse all categories;
* - add link to 'root' category => category-less link is always visible.
*
* @author Patrick Cool (December 2003 - January 2004)
* @author René Haentjens, CSV file import (October 2004)
*/
class Link extends Model
{
public $table;
public $is_course_model = true;
public $columns = [
'id',
'c_id',
'url',
'title',
'description',
'category_id',
'on_homepage',
'target',
'session_id',
];
public array $required = ['url', 'title'];
private $course;
public function __construct()
{
$this->table = Database::get_course_table(TABLE_LINK);
}
/**
* @param array $course
*/
public function setCourse($course)
{
$this->course = $course;
}
/**
* @return array
*/
public function getCourse()
{
return !empty($this->course) ? $this->course : api_get_course_info();
}
/**
* Organize the saving of a link, using the parent's save method and
* updating the item_property table.
*
* @param array $params
* @param bool $showQuery Whether to show the query in logs when
* calling parent's save method
*
* @return bool True if link could be saved, false otherwise
*/
public function save($params, $showQuery = null, $showFlash = true)
{
$course_info = $this->getCourse();
$course_id = $course_info['real_id'];
$session_id = api_get_session_id();
$title = stripslashes($params['title']);
$urllink = $params['url'];
$description = $params['description'];
$categoryId = (int) $params['category_id'];
$onhomepage = 0;
if (isset($params['on_homepage'])) {
$onhomepage = Security::remove_XSS($params['on_homepage']);
}
$target = '_self'; // Default target.
if (!empty($params['target'])) {
$target = Security::remove_XSS($params['target']);
}
$urllink = trim($urllink);
$title = trim($title);
$description = trim($description);
// We ensure URL to be absolute.
if (false === strpos($urllink, '://')) {
$urllink = 'http://'.$urllink;
}
// If the title is empty, we use the URL as title.
if ('' == $title) {
$title = $urllink;
}
// If the URL is invalid, an error occurs.
if (!api_valid_url($urllink, true)) {
// A check against an absolute URL
Display::addFlash(
Display::return_message(get_lang('Please give the link URL, it should be valid.'), 'error')
);
return false;
} else {
$category = null;
$repoCategory = Container::getLinkCategoryRepository();
if (!empty($categoryId)) {
/** @var CLinkCategory $category */
$category = $repoCategory->find($categoryId);
}
// Looking for the largest order number for this category.
$link = new CLink();
$link
->setUrl($urllink)
->setTitle($title)
->setDescription($description)
->setTarget($target)
->setCategory($category)
;
$repo = Container::getLinkRepository();
$courseEntity = api_get_course_entity($course_id);
if (empty($category)) {
$link
->setParent($courseEntity)
->addCourseLink($courseEntity, api_get_session_entity($session_id))
;
} else {
$link
->setParent($category)
->addCourseLink($courseEntity, api_get_session_entity($session_id))
;
}
$repo->create($link);
$link_id = $link->getIid();
if (('true' === api_get_setting('search_enabled')) &&
$link_id && extension_loaded('xapian')
) {
$courseCode = $course_info['code'];
$specific_fields = get_specific_field_list();
$ic_slide = new IndexableChunk();
// Add all terms to db.
$all_specific_terms = '';
foreach ($specific_fields as $specific_field) {
if (isset($_REQUEST[$specific_field['code']])) {
$sterms = trim($_REQUEST[$specific_field['code']]);
if (!empty($sterms)) {
$all_specific_terms .= ' '.$sterms;
$sterms = explode(',', $sterms);
foreach ($sterms as $sterm) {
$ic_slide->addTerm(
trim($sterm),
$specific_field['code']
);
add_specific_field_value(
$specific_field['id'],
$courseCode,
TOOL_LINK,
$link_id,
$sterm
);
}
}
}
}
// Build the chunk to index.
$ic_slide->addValue('title', $title);
$ic_slide->addCourseId($courseCode);
$ic_slide->addToolId(TOOL_LINK);
$xapian_data = [
SE_COURSE_ID => $courseCode,
SE_TOOL_ID => TOOL_LINK,
SE_DATA => [
'link_id' => $link_id,
],
SE_USER => (int) api_get_user_id(),
];
$ic_slide->xapian_data = serialize($xapian_data);
$description = $all_specific_terms.' '.$description;
$ic_slide->addValue('content', $description);
// Add category name if set.
if (isset($categoryId) && $categoryId > 0) {
$table_link_category = Database::get_course_table(
TABLE_LINK_CATEGORY
);
$sql_cat = 'SELECT * FROM %s WHERE id=%d AND c_id = %d LIMIT 1';
$sql_cat = sprintf(
$sql_cat,
$table_link_category,
$categoryId,
$course_id
);
$result = Database:: query($sql_cat);
if (1 == Database:: num_rows($result)) {
$row = Database:: fetch_array($result);
$ic_slide->addValue(
'category',
$row['title']
);
}
}
$di = new ChamiloIndexer();
isset($params['language']) ? $lang = Database:: escape_string(
$params['language']
) : $lang = 'english';
$di->connectDb(null, null, $lang);
$di->addChunk($ic_slide);
// Index and return search engine document id.
$did = $di->index();
if ($did) {
// Save it to db.
$tbl_se_ref = Database::get_main_table(
TABLE_MAIN_SEARCH_ENGINE_REF
);
$sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
VALUES (NULL , \'%s\', \'%s\', %s, %s)';
$sql = sprintf(
$sql,
$tbl_se_ref,
$course_id,
$courseCode,
TOOL_LINK,
$link_id,
$did
);
Database:: query($sql);
}
}
if ($showFlash) {
Display::addFlash(Display::return_message(get_lang('The link has been added.')));
}
return $link_id;
}
}
/**
* Update a link in the database.
*
* @param int $linkId The ID of the link to update
* @param string $linkUrl The new URL to be saved
* @param int $courseId
* @param int $sessionId
*
* @return bool
*/
public function updateLink(
$linkId,
$linkUrl,
$courseId = null,
$sessionId = null
) {
$tblLink = Database::get_course_table(TABLE_LINK);
$linkUrl = Database::escape_string($linkUrl);
$linkId = (int) $linkId;
if ('' != $linkUrl) {
$sql = "UPDATE $tblLink SET
url = '$linkUrl'
WHERE iid = $linkId ";
$resLink = Database::query($sql);
return $resLink;
}
return false;
}
public static function addCategory()
{
$_course = api_get_course_info();
$course_id = $_course['real_id'];
$category_title = trim($_POST['category_title']);
$description = trim($_POST['description']);
if (empty($category_title)) {
echo Display::return_message(get_lang('Please give the category name'), 'error');
return false;
}
// Looking for the largest order number for this category.
/*$result = Database:: query(
"SELECT MAX(display_order) FROM $tbl_categories
WHERE c_id = $course_id "
);
[$orderMax] = Database:: fetch_row($result);
$order = $orderMax + 1;
$order = (int) $order;*/
$session_id = api_get_session_id();
$repo = Container::getLinkCategoryRepository();
$courseEntity = api_get_course_entity($course_id);
$sessionEntity = api_get_session_entity($session_id);
$category = (new CLinkCategory())
->setTitle($category_title)
->setDescription($description)
// ->setDisplayOrder($order)
->setParent($courseEntity)
->addCourseLink($courseEntity, $sessionEntity)
;
$repo->create($category);
Display::addFlash(Display::return_message(get_lang('Category added')));
return $category;
}
public static function deleteCategory($id)
{
$repo = Container::getLinkCategoryRepository();
/** @var CLinkCategory $category */
$category = $repo->find($id);
if ($category) {
$repo->delete($category);
Display::addFlash(Display::return_message(get_lang('The category has been deleted.')));
return true;
}
return false;
}
/**
* Used to delete a link.
*
* @param int $id
*
* @return bool
*/
public static function deleteLink($id)
{
$repo = Container::getLinkRepository();
$link = $repo->find($id);
if ($link) {
$repo->delete($link);
self::delete_link_from_search_engine(api_get_course_id(), $id);
SkillModel::deleteSkillsFromItem($id, ITEM_TYPE_LINK);
Display::addFlash(Display::return_message(get_lang('The link has been deleted')));
return true;
}
return false;
}
/**
* Removes a link from search engine database.
*
* @param string $course_id Course code
* @param int $link_id Document id to delete
*/
public static function delete_link_from_search_engine($course_id, $link_id)
{
// Remove from search engine if enabled.
if ('true' === api_get_setting('search_enabled')) {
$tbl_se_ref = Database::get_main_table(
TABLE_MAIN_SEARCH_ENGINE_REF
);
$sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
$sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_LINK, $link_id);
$res = Database:: query($sql);
if (Database:: num_rows($res) > 0) {
$row = Database::fetch_array($res);
$di = new ChamiloIndexer();
$di->remove_document($row['search_did']);
}
$sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
$sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_LINK, $link_id);
Database:: query($sql);
// Remove terms from db.
delete_all_values_for_item($course_id, TOOL_DOCUMENT, $link_id);
}
}
/**
* Get link info.
*
* @param int $id
*
* @return array link info
*/
public static function getLinkInfo($id)
{
$tbl_link = Database::get_course_table(TABLE_LINK);
$course_id = api_get_course_int_id();
$id = (int) $id;
if (empty($id) || empty($course_id)) {
return [];
}
$sql = "SELECT * FROM $tbl_link
WHERE iid= $id ";
$result = Database::query($sql);
$data = [];
if (Database::num_rows($result)) {
$data = Database::fetch_array($result);
}
return $data;
}
/**
* @param int $id
* @param array $values
*/
public static function editLink($id, $values = [])
{
$_course = api_get_course_info();
$course_id = $_course['real_id'];
$id = (int) $id;
$values['url'] = trim($values['url']);
$values['title'] = trim($values['title']);
$values['description'] = trim($values['description']);
$values['target'] = empty($values['target']) ? '_self' : $values['target'];
$values['on_homepage'] = isset($values['on_homepage']) ? $values['on_homepage'] : '';
$categoryId = (int) $values['category_id'];
// We ensure URL to be absolute.
if (false === strpos($values['url'], '://')) {
$values['url'] = 'http://'.$_POST['url'];
}
// If the title is empty, we use the URL as title.
if ('' == $values['title']) {
$values['title'] = $values['url'];
}
// If the URL is invalid, an error occurs.
if (!api_valid_url($values['url'], true)) {
Display::addFlash(
Display::return_message(get_lang('Please give the link URL, it should be valid.'), 'error')
);
return false;
}
if (empty($id) || empty($course_id)) {
return false;
}
$repo = Container::getLinkRepository();
/** @var CLink $link */
$link = $repo->find($id);
if (null === $link) {
return false;
}
/*if ($link->getCategory() != $values['category_id']) {
$sql = "SELECT MAX(display_order)
FROM $tbl_link
WHERE
category_id='".intval($values['category_id'])."'";
$result = Database:: query($sql);
[$max_display_order] = Database:: fetch_row($result);
$max_display_order++;
} else {
$max_display_order = $row['display_order'];
}*/
$link
->setUrl($values['url'])
->setTitle($values['title'])
->setDescription($values['description'])
->setTarget($values['target'])
;
$courseEntity = api_get_course_entity($course_id);
if ($categoryId) {
$repoCategory = Container::getLinkCategoryRepository();
/** @var CLinkCategory $category */
$category = $repoCategory->find($categoryId);
$link
->setCategory($category)
->setParent($category);
} else {
$link
->setCategory(null)
->setParent($courseEntity);
}
$repo->updateNodeForResource($link);
$repo->update($link);
// Update search enchine and its values table if enabled.
if ('true' === api_get_setting('search_enabled')) {
$course_int_id = api_get_course_int_id();
$course_id = api_get_course_id();
$link_title = Database:: escape_string($values['title']);
$link_description = Database:: escape_string($values['description']);
// Actually, it consists on delete terms from db,
// insert new ones, create a new search engine document, and remove the old one.
// Get search_did.
$tbl_se_ref = Database::get_main_table(
TABLE_MAIN_SEARCH_ENGINE_REF
);
$sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
$sql = sprintf(
$sql,
$tbl_se_ref,
$course_id,
TOOL_LINK,
$id
);
$res = Database:: query($sql);
if (Database:: num_rows($res) > 0) {
$se_ref = Database:: fetch_array($res);
$specific_fields = get_specific_field_list();
$ic_slide = new IndexableChunk();
$all_specific_terms = '';
foreach ($specific_fields as $specific_field) {
delete_all_specific_field_value(
$course_id,
$specific_field['id'],
TOOL_LINK,
$id
);
if (isset($_REQUEST[$specific_field['code']])) {
$sterms = trim(
$_REQUEST[$specific_field['code']]
);
if (!empty($sterms)) {
$all_specific_terms .= ' '.$sterms;
$sterms = explode(',', $sterms);
foreach ($sterms as $sterm) {
$ic_slide->addTerm(
trim($sterm),
$specific_field['code']
);
add_specific_field_value(
$specific_field['id'],
$course_id,
TOOL_LINK,
$id,
$sterm
);
}
}
}
}
// Build the chunk to index.
$ic_slide->addValue("title", $link_title);
$ic_slide->addCourseId($course_id);
$ic_slide->addToolId(TOOL_LINK);
$xapian_data = [
SE_COURSE_ID => $course_id,
SE_TOOL_ID => TOOL_LINK,
SE_DATA => [
'link_id' => $id,
],
SE_USER => (int) api_get_user_id(),
];
$ic_slide->xapian_data = serialize($xapian_data);
$link_description = $all_specific_terms.' '.$link_description;
$ic_slide->addValue('content', $link_description);
// Add category name if set.
if (isset($categoryId) && $categoryId > 0) {
$table_link_category = Database::get_course_table(
TABLE_LINK_CATEGORY
);
$sql_cat = 'SELECT * FROM %s WHERE id=%d and c_id = %d LIMIT 1';
$sql_cat = sprintf(
$sql_cat,
$table_link_category,
$categoryId,
$course_int_id
);
$result = Database:: query($sql_cat);
if (1 == Database:: num_rows($result)) {
$row = Database:: fetch_array($result);
$ic_slide->addValue(
'category',
$row['title']
);
}
}
$di = new ChamiloIndexer();
isset($_POST['language']) ? $lang = Database:: escape_string($_POST['language']) : $lang = 'english';
$di->connectDb(null, null, $lang);
$di->remove_document($se_ref['search_did']);
$di->addChunk($ic_slide);
// Index and return search engine document id.
$did = $di->index();
if ($did) {
// Save it to db.
$sql = 'DELETE FROM %s
WHERE course_code=\'%s\'
AND tool_id=\'%s\'
AND ref_id_high_level=\'%s\'';
$sql = sprintf(
$sql,
$tbl_se_ref,
$course_id,
TOOL_LINK,
$id
);
Database:: query($sql);
$sql = 'INSERT INTO %s (c_id, id, course_code, tool_id, ref_id_high_level, search_did)
VALUES (NULL , \'%s\', \'%s\', %s, %s)';
$sql = sprintf(
$sql,
$tbl_se_ref,
$course_int_id,
$course_id,
TOOL_LINK,
$id,
$did
);
Database:: query($sql);
}
}
}
Display::addFlash(Display::return_message(get_lang('The link has been modified.')));
}
/**
* @param int $id
* @param array $values
*
* @return bool
*/
public static function editCategory($id, $values)
{
$repo = Container::getLinkCategoryRepository();
/** @var CLinkCategory $category */
$category = $repo->find($id);
$category
->setTitle($values['category_title'])
->setDescription($values['description'])
;
$repo->update($category);
Display::addFlash(Display::return_message(get_lang('The category has been modified.')));
return true;
}
/**
* Changes the visibility of a link.
*/
public static function setVisible($id, $scope)
{
if (TOOL_LINK == $scope) {
/*api_item_property_update(
$_course,
TOOL_LINK,
$id,
$_GET['action'],
$_user['user_id']
);*/
$repo = Container::getLinkRepository();
/** @var CLink $link */
$link = $repo->find($id);
if ($link) {
$repo->setVisibilityPublished($link);
}
} elseif (TOOL_LINK_CATEGORY == $scope) {
$repo = Container::getLinkCategoryRepository();
/** @var CLink $link */
$link = $repo->find($id);
if ($link) {
$repo->setVisibilityPublished($link);
}
/*api_item_property_update(
$_course,
TOOL_LINK_CATEGORY,
$id,
$_GET['action'],
$_user['user_id']
);*/
}
Display::addFlash(Display::return_message(get_lang('The visibility has been changed.')));
}
public static function setInvisible($id, $scope)
{
if (TOOL_LINK == $scope) {
$repo = Container::getLinkRepository();
/** @var CLink $link */
$link = $repo->find($id);
if ($link) {
$repo->setVisibilityDraft($link);
}
} elseif (TOOL_LINK_CATEGORY == $scope) {
$repo = Container::getLinkCategoryRepository();
/** @var CLinkCategory $link */
$link = $repo->find($id);
if ($link) {
$repo->setVisibilityDraft($link);
}
}
Display::addFlash(Display::return_message(get_lang('The visibility has been changed.')));
}
/**
* Generate SQL to select all the links categories in the current course and
* session.
*
* @param int $courseId
* @param int $sessionId
* @param bool $withBaseContent
*
* @return CLinkCategory[]
*/
public static function getLinkCategories($courseId, $sessionId, $withBaseContent = true)
{
$repo = Container::getLinkCategoryRepository();
$courseEntity = api_get_course_entity($courseId);
$sessionEntity = api_get_session_entity($sessionId);
$qb = $repo->getResourcesByCourse($courseEntity, $sessionEntity);
return $qb->getQuery()->getResult();
/*
$tblLinkCategory = Database::get_course_table(TABLE_LINK_CATEGORY);
$tblItemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
$courseId = (int) $courseId;
$courseInfo = api_get_course_info_by_id($courseId);
// Condition for the session.
$sessionCondition = api_get_session_condition(
$sessionId,
true,
$withBaseContent,
'linkcat.session_id'
);
// Getting links
$sql = "SELECT *, linkcat.id
FROM $tblLinkCategory linkcat
WHERE
linkcat.c_id = $courseId
$sessionCondition
ORDER BY linkcat.display_order DESC";
$result = Database::query($sql);
$categories = Database::store_result($result);
$sql = "SELECT *, linkcat.id
FROM $tblLinkCategory linkcat
INNER JOIN $tblItemProperty ip
ON (linkcat.id = ip.ref AND linkcat.c_id = ip.c_id)
WHERE
ip.tool = '".TOOL_LINK_CATEGORY."' AND
(ip.visibility = '0' OR ip.visibility = '1')
$sessionCondition AND
linkcat.c_id = ".$courseId."
ORDER BY linkcat.display_order DESC";
$result = Database::query($sql);
$categoryInItemProperty = [];
if (Database::num_rows($result)) {
while ($row = Database::fetch_assoc($result)) {
$categoryInItemProperty[$row['id']] = $row;
}
}
foreach ($categories as &$category) {
if (!isset($categoryInItemProperty[$category['id']])) {
api_item_property_update(
$courseInfo,
TOOL_LINK_CATEGORY,
$category['id'],
'LinkCategoryAdded',
api_get_user_id()
);
}
}
$sql = "SELECT DISTINCT linkcat.*, visibility
FROM $tblLinkCategory linkcat
INNER JOIN $tblItemProperty ip
ON (linkcat.id = ip.ref AND linkcat.c_id = ip.c_id)
WHERE
ip.tool = '".TOOL_LINK_CATEGORY."' AND
(ip.visibility = '0' OR ip.visibility = '1')
$sessionCondition AND
linkcat.c_id = ".$courseId."
ORDER BY linkcat.display_order DESC
";
$result = Database::query($sql);
return Database::store_result($result, 'ASSOC');*/
}
/**
* @param int $categoryId
* @param $courseId
* @param $sessionId
* @param bool $withBaseContent
*
* @return CLink[]|null
*/
public static function getLinksPerCategory(
$categoryId,
$courseId,
$sessionId,
$withBaseContent = true
) {
$courseEntity = api_get_course_entity($courseId);
$sessionEntity = api_get_session_entity($sessionId);
if (empty($categoryId)) {
$repo = Container::getLinkRepository();
$qb = $repo->getResourcesByCourse($courseEntity, $sessionEntity, null, $courseEntity->getResourceNode());
return $qb->getQuery()->getResult();
}
$repo = Container::getLinkCategoryRepository();
/** @var CLinkCategory $category */
$category = $repo->find($categoryId);
if ($category) {
$repo = Container::getLinkRepository();
$qb = $repo->getResourcesByCourse($courseEntity, $sessionEntity, null, $category->getResourceNode());
return $qb->getQuery()->getResult();
}
return null;
}
/**
* Displays all the links of a given category.
*
* @param int $categoryId
* @param int $courseId
* @param int $sessionId
* @param bool $showActionLinks
*
* @return string
*/
public static function showLinksPerCategory($categoryId, $courseId, $sessionId, $showActionLinks = true)
{
global $token;
$links = self::getLinksPerCategory($categoryId, $courseId, $sessionId);
$content = '';
$numberOfLinks = count($links);
if (!empty($links)) {
$courseEntity = api_get_course_entity($courseId);
$sessionEntity = api_get_session_entity($sessionId);
$user = api_get_user_entity();
$i = 1;
$linksAdded = [];
$iconLink = Display::getMdiIcon(ObjectIcon::LINK, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Link'));
foreach ($links as $link) {
$linkId = $link->getIid();
$resourceLink = $link->getFirstResourceLink();
if (in_array($linkId, $linksAdded)) {
continue;
}
$visibility = (int) $link->isVisible($courseEntity, $sessionEntity);
$linksAdded[] = $linkId;
$categoryId = 0;
if ($link->getCategory()) {
$categoryId = $link->getCategory()->getIid();
}
// Validation when belongs to a session.
$session_img = '';
$session = $resourceLink->getSession();
if ($session) {
$session_img = api_get_session_image($session->getId(), $user);
}
$toolbar = '';
$link_validator = '';
if (api_is_allowed_to_edit(null, true)) {
$toolbar .= Display::toolbarButton(
'',
'javascript:void(0);',
'check-circle',
'secondary btn-sm',
[
'onclick' => "check_url('".$linkId."', '".addslashes($link->getUrl())."');",
'title' => get_lang('Check link'),
]
);
$link_validator .= Display::span(
'',
[
'id' => 'url_id_'.$linkId,
'class' => 'check-link',
]
);
if ($session === $sessionEntity) {
$url = api_get_self().'?'.api_get_cidreq().'&action=editlink&id='.$linkId;
$title = get_lang('Edit');
$toolbar .= Display::toolbarButton(
'',
$url,
'pencil',
'secondary btn-sm',
[
'title' => $title,
]
);
}
$urlVisibility = api_get_self().'?'.api_get_cidreq().
'&sec_token='.$token.
'&id='.$linkId.
'&scope=link&category_id='.$categoryId;
switch ($visibility) {
case 1:
$urlVisibility .= '&action=invisible';
$title = get_lang('Make invisible');
$toolbar .= Display::toolbarButton(
'',
$urlVisibility,
'eye',
'secondary btn-sm',
[
'title' => $title,
]
);
break;
case 0:
$urlVisibility .= '&action=visible';
$title = get_lang('Make Visible');
$toolbar .= Display::toolbarButton(
'',
$urlVisibility,
'eye-off',
'secondary btn-sm',
[
'title' => $title,
]
);
break;
}
if ($session === $sessionEntity) {
$moveLinkParams = [
'id' => $linkId,
'scope' => 'category',
'category_id' => $categoryId,
'action' => 'move_link_up',
];
$toolbar .= Display::toolbarButton(
get_lang('Move up'),
api_get_self().'?'.api_get_cidreq().'&'.http_build_query($moveLinkParams),
'level-up-alt',
'secondary',
['class' => 'btn-sm '.(1 === $i ? 'disabled' : '')],
false
);
$moveLinkParams['action'] = 'move_link_down';
$toolbar .= Display::toolbarButton(
get_lang('Move down'),
api_get_self().'?'.api_get_cidreq().'&'.http_build_query($moveLinkParams),
'level-down-alt',
'secondary',
['class' => 'btn-sm '.($i === $numberOfLinks ? 'disabled' : '')],
false
);
$url = api_get_self().'?'.api_get_cidreq().'&sec_token='.$token.'&action=deletelink&id='.$linkId.'&category_id='.$categoryId;
$event = "javascript: if(!confirm('".get_lang('Do you want to delete this link?')."'))return false;";
$title = get_lang('Delete');
$toolbar .= Display::toolbarButton(
'',
$url,
'delete',
'secondary btn-sm',
[
'onclick' => $event,
'title' => $title,
]
);
}
}
$showLink = false;
$titleClass = '';
if ($visibility) {
$showLink = true;
} else {
if (api_is_allowed_to_edit(null, true)) {
$showLink = true;
$titleClass = 'text-muted';
}
}
if ($showLink) {
$url = api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$linkId.'&link_url='.urlencode($link->getUrl());
$actions = '';
if ($showActionLinks) {
$actions .= $toolbar;
}
$title = $iconLink;
$title .= Display::tag(
'a',
Security::remove_XSS($link->getTitle()),
[
'href' => $url,
'target' => $link->getTarget(),
'class' => $titleClass,
]
);
$title .= $link_validator;
$title .= $session_img;
$content .= Display::panel(null, $title, null, null, null, null, null, $actions);
}
$i++;
}
}
return $content;
}
/**
* Displays the edit, delete and move icons.
*
* @param int Category ID
* @param int $currentCategory
* @param int $countCategories
*
* @return string
*
* @author Patrick Cool <patrick.cool@UGent.be>, Ghent University
*/
public static function showCategoryAdminTools(CLinkCategory $category, $currentCategory, $countCategories)
{
$categoryId = $category->getIid();
$token = null;
$tools = '<a href="'.api_get_self().'?'.api_get_cidreq().'&sec_token='.$token.'&action=editcategory&id='.$categoryId.'&category_id='.$categoryId.'" title='.get_lang('Edit').'">'.
Display::getMdiIcon(
ActionIcon::EDIT,
'ch-tool-icon',
null,
ICON_SIZE_SMALL,
get_lang('Edit')
).'</a>';
// DISPLAY MOVE UP COMMAND only if it is not the top link.
if (0 != $currentCategory) {
$tools .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&sec_token='.$token.'&action=up&up='.$categoryId.'&category_id='.$categoryId.'" title="'.get_lang('Up').'">'.
Display::getMdiIcon(
ActionIcon::UP,
'ch-tool-icon',
null,
ICON_SIZE_SMALL,
get_lang('Up')
).'</a>';
} else {
$tools .= Display::getMdiIcon(
ActionIcon::UP,
'ch-tool-icon-disabled',
null,
ICON_SIZE_SMALL,
get_lang('Up')
).'</a>';
}
// DISPLAY MOVE DOWN COMMAND only if it is not the bottom link.
if ($currentCategory < $countCategories - 1) {
$tools .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&sec_token='.$token.'&action=down&down='.$categoryId.'&category_id='.$categoryId.'">'.
Display::getMdiIcon(
ActionIcon::DOWN,
'ch-tool-icon',
null,
ICON_SIZE_SMALL,
get_lang('down')
).'</a>';
} else {
$tools .= Display::getMdiIcon(
ActionIcon::DOWN,
'ch-tool-icon-disabled',
null,
ICON_SIZE_SMALL,
get_lang('down')
).'</a>';
}
$tools .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&sec_token='.$token.'&action=deletecategory&id='.$categoryId."&category_id=$categoryId\"
onclick=\"javascript: if(!confirm('".get_lang('When deleting a category, all links of this category are also deleted.
Do you really want to delete this category and its links ?')."')) return false;\">".
Display::getMdiIcon(
ActionIcon::DELETE,
'ch-tool-icon',
null,
ICON_SIZE_SMALL,
get_lang('Delete')
).'</a>';
return $tools;
}
/**
* move a link or a linkcategory up or down.
*
* @param int Category ID
* @param int Course ID
* @param int Session ID
*
* @author Patrick Cool <patrick.cool@UGent.be>, Ghent University
*
* @todo support sessions
*/
public static function movecatlink($action, $catlinkid, $courseId = null, $sessionId = null)
{
$tbl_categories = Database::get_course_table(TABLE_LINK_CATEGORY);
if (is_null($courseId)) {
$courseId = api_get_course_int_id();
}
$courseId = intval($courseId);
if (is_null($sessionId)) {
$sessionId = api_get_session_id();
}
$sessionId = intval($sessionId);
$thiscatlinkId = intval($catlinkid);
if ('down' == $action) {
$sortDirection = 'DESC';
}
if ('up' == $action) {
$sortDirection = 'ASC';
}
$movetable = $tbl_categories;
if (!empty($sortDirection)) {
if (!in_array(trim(strtoupper($sortDirection)), ['ASC', 'DESC'])) {
$sortDirection = 'ASC';
}
$sql = "SELECT id, display_order FROM $tbl_categories
WHERE c_id = $courseId
ORDER BY display_order $sortDirection";
$linkresult = Database:: query($sql);
$thislinkOrder = 1;
while ($sortrow = Database:: fetch_array($linkresult)) {
// STEP 2 : FOUND THE NEXT LINK ID AND ORDER, COMMIT SWAP
// This part seems unlogic, but it isn't . We first look for the current link with the querystring ID
// and we know the next iteration of the while loop is the next one. These should be swapped.
if (isset($thislinkFound) && $thislinkFound) {
$nextlinkId = $sortrow['id'];
$nextlinkOrder = $sortrow['display_order'];
Database:: query(
"UPDATE ".$movetable."
SET display_order = '$nextlinkOrder'
WHERE c_id = $courseId AND id = '$thiscatlinkId'"
);
Database:: query(
"UPDATE ".$movetable."
SET display_order = '$thislinkOrder'
WHERE c_id = $courseId AND id = '$nextlinkId'"
);
break;
}
if ($sortrow['id'] == $thiscatlinkId) {
$thislinkOrder = $sortrow['display_order'];
$thislinkFound = true;
}
}
}
Display::addFlash(Display::return_message(get_lang('LinksMoved')));
}
/**
* This function checks if the url is a vimeo link.
*
* @author Julio Montoya
*
* @version 1.0
*/
public static function isVimeoLink($url)
{
$isLink = strrpos($url, 'vimeo.com');
return $isLink;
}
/**
* Get vimeo id from URL.
*
* @param string $url
*
* @return bool|mixed
*/
public static function getVimeoLinkId($url)
{
$possibleUrls = [
'http://www.vimeo.com/',
'http://vimeo.com/',
'https://www.vimeo.com/',
'https://vimeo.com/',
];
$url = str_replace($possibleUrls, '', $url);
if (is_numeric($url)) {
return $url;
}
return false;
}
/**
* This function checks if the url is a youtube link.
*
* @author Jorge Frisancho
* @author Julio Montoya - Fixing code
*
* @version 1.0
*/
public static function is_youtube_link($url)
{
$is_youtube_link = strrpos($url, 'youtube') || strrpos(
$url,
'youtu.be'
);
return $is_youtube_link;
}
/**
* This function checks if the url is a PDF File link.
*
* @author Jorge Frisancho
* @author Alex Aragón - Fixing code
*
* @version 1.0
*/
public static function isPdfLink($url)
{
$isPdfLink = strrpos(strtolower($url), '.pdf');
return $isPdfLink;
}
/**
* Get youtube id from an URL.
*
* @param string $url
*
* @return string
*/
public static function get_youtube_video_id($url)
{
// This is the length of YouTube's video IDs
$len = 11;
// The ID string starts after "v=", which is usually right after
// "youtube.com/watch?" in the URL
$pos = strpos($url, "v=");
$id = '';
//If false try other options
if (false === $pos) {
$url_parsed = parse_url($url);
//Youtube shortener
//http://youtu.be/ID
$pos = strpos($url, "youtu.be");
if (false == $pos) {
$id = '';
} else {
return substr($url_parsed['path'], 1);
}
//if empty try the youtube.com/embed/ID
if (empty($id)) {
$pos = strpos($url, "embed");
if (false === $pos) {
return '';
} else {
return substr($url_parsed['path'], 7);
}
}
} else {
// Offset the start location to match the beginning of the ID string
$pos += 2;
// Get the ID string and return it
$id = substr($url, $pos, $len);
return $id;
}
}
/**
* @param int $courseId
* @param int $sessionId
* @param int $categoryId
* @param string $show
* @param null $token
* @param bool $showActionLinks
* @param bool $forceOpenCategories
*
* @return string
*/
public static function listLinksAndCategories(
$courseId,
$sessionId,
$categoryId,
$show = 'none',
$token = null,
$showActionLinks = true,
$forceOpenCategories = false
) {
$categoryId = (int) $categoryId;
$content = '';
$toolbar = '';
$categories = self::getLinkCategories($courseId, $sessionId);
$countCategories = count($categories);
$linksPerCategory = self::showLinksPerCategory(0, $courseId, $sessionId, $showActionLinks);
if ($showActionLinks) {
/* Action Links */
$actions = '';
if (api_is_allowed_to_edit(null, true)) {
$actions .= '<a
href="'.api_get_self().'?'.api_get_cidreq().'&action=addlink&category_id='.$categoryId.'">'.
Display::getMdiIcon(ActionIcon::ADD, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Add a link')).'</a>';
$actions .= '<a
href="'.api_get_self().'?'.api_get_cidreq().'&action=addcategory&category_id='.$categoryId.'">'.
Display::getMdiIcon(ActionIcon::CREATE_CATEGORY, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Add a category')).'</a>';
}
if (!empty($countCategories)) {
$actions .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=list&show=none">';
$actions .= Display::getMdiIcon(StateIcon::LIST_VIEW, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('List View')).' </a>';
$actions .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=list&show=all">';
$actions .= Display::getMdiIcon(StateIcon::NESTED_VIEW, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Nested View')).'</a>';
}
$actions .= Display::url(
Display::getMdiIcon(ActionIcon::EXPORT_PDF, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Export to PDF')),
api_get_self().'?'.api_get_cidreq().'&action=export'
);
$toolbar = Display::toolbarAction('toolbar', [$actions]);
}
if (empty($countCategories)) {
$content .= $linksPerCategory;
} else {
if (!empty($linksPerCategory)) {
$content .= Display::panel($linksPerCategory, get_lang('General'));
}
}
$counter = 0;
$courseEntity = api_get_course_entity($courseId);
$sessionEntity = api_get_session_entity($sessionId);
$allowToEdit = api_is_allowed_to_edit(null, true);
foreach ($categories as $category) {
$categoryItemId = $category->getIid();
$isVisible = $category->isVisible($courseEntity, $sessionEntity);
// Student don't see invisible categories.
if (!$allowToEdit) {
if (!$isVisible) {
continue;
}
}
// Validation when belongs to a session
$showChildren = $categoryId === $categoryItemId || 'all' === $show;
if ($forceOpenCategories) {
$showChildren = true;
}
$strVisibility = '';
$visibilityClass = null;
if ($isVisible) {
$strVisibility = '<a
href="link.php?'.api_get_cidreq().'&sec_token='.$token.'&action=invisible&id='.$categoryItemId.'&scope='.TOOL_LINK_CATEGORY.'"
title="'.get_lang('Hide').'">'.
Display::getMdiIcon(StateIcon::ACTIVE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Hide')).'</a>';
} elseif (!$isVisible) {
$visibilityClass = 'text-muted';
$strVisibility = ' <a
href="link.php?'.api_get_cidreq().'&sec_token='.$token.'&action=visible&id='.$categoryItemId.'&scope='.TOOL_LINK_CATEGORY.'"
title="'.get_lang('Show').'">'.
Display::getMdiIcon(StateIcon::INACTIVE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Show')).'</a>';
}
$header = '';
if ($showChildren) {
$header .= '<a
class="'.$visibilityClass.'" href="'.api_get_self().'?'.api_get_cidreq().'&category_id=">';
$header .= Display::getMdiIcon(StateIcon::NESTED_VIEW, 'ch-tool-icon', null, ICON_SIZE_SMALL);
} else {
$header .= '<a
class="'.$visibilityClass.'"
href="'.api_get_self().'?'.api_get_cidreq().'&category_id='.$categoryItemId.'">';
$header .= Display::getMdiIcon(StateIcon::LIST_VIEW, 'ch-tool-icon', null, ICON_SIZE_SMALL);
}
$header .= Security::remove_XSS($category->getTitle()).'</a>';
if ($showActionLinks) {
if ($allowToEdit) {
if ($category->getFirstResourceLink() &&
$sessionEntity === $category->getFirstResourceLink()->getSession()
) {
$header .= $strVisibility;
$header .= self::showCategoryAdminTools($category, $counter, count($categories));
} else {
$header .= get_lang('Edition not available from the session, please edit from the basic course.');
}
}
}
$childrenContent = '';
if ($showChildren) {
$childrenContent = self::showLinksPerCategory(
$categoryItemId,
api_get_course_int_id(),
api_get_session_id()
);
}
$content .= Display::panel($category->getDescription().$childrenContent, $header);
$counter++;
}
if (empty($content) && api_is_allowed_to_edit()) {
$content .= Display::noDataView(
get_lang('Links'),
Display::getMdiIcon(ObjectIcon::LINK, 'ch-tool-icon', null, ICON_SIZE_BIG),
get_lang('Add links'),
api_get_self().'?'.api_get_cidreq().'&'.http_build_query(['action' => 'addlink'])
);
}
return $toolbar.$content;
}
/**
* @param int $linkId
* @param string $action
* @param null $token
*
* @return FormValidator
*/
public static function getLinkForm($linkId, $action, $token = null)
{
$courseId = api_get_course_int_id();
$sessionId = api_get_session_id();
$linkInfo = self::getLinkInfo($linkId);
$categoryId = isset($linkInfo['category_id']) ? $linkInfo['category_id'] : '';
$lpId = isset($_GET['lp_id']) ? Security::remove_XSS($_GET['lp_id']) : null;
$form = new FormValidator(
'link',
'post',
api_get_self().'?action='.$action.
'&category_id='.$categoryId.
'&'.api_get_cidreq().
'&id='.$linkId.
'&sec_token='.$token
);
if ('addlink' === $action) {
$form->addHeader(get_lang('Add a link'));
} else {
$form->addHeader(get_lang('Edit link'));
}
$target_link = '_blank';
$title = '';
$category = '';
$onhomepage = '';
$description = '';
if (!empty($linkInfo)) {
$urllink = $linkInfo['url'];
$title = $linkInfo['title'];
$description = $linkInfo['description'];
$category = $linkInfo['category_id'];
if (isset($linkInfo['on_homepage']) && 0 != $linkInfo['on_homepage']) {
$onhomepage = 1;
}
$target_link = $linkInfo['target'];
}
$form->addHidden('id', $linkId);
$form->addText('url', 'URL');
$form->addRule('url', get_lang('Please give the link URL, it should be valid.'), 'url');
$form->addText('title', get_lang('Link name'));
$form->addHtmlEditor(
'description',
get_lang('Description'),
false,
false,
['ToolbarSet' => 'Profile', 'Width' => '100%', 'Height' => '130']
);
$resultcategories = self::getLinkCategories($courseId, $sessionId);
$options = ['0' => '--'];
if (!empty($resultcategories)) {
foreach ($resultcategories as $myrow) {
$options[$myrow->getIid()] = $myrow->getTitle();
}
}
$form->addSelect('category_id', get_lang('Category'), $options);
$form->addCheckBox('on_homepage', null, get_lang('Show link on course homepage'));
$targets = [
'_self' => get_lang('Open self'),
'_blank' => get_lang('Open blank'),
'_parent' => get_lang('Open parent'),
'_top' => get_lang('Open top'),
];
$form->addSelect(
'target',
[
get_lang('Link\'s target'),
get_lang('Select the target which shows the link on the homepage of the course'),
],
$targets
);
$defaults = [
'url' => empty($urllink) ? 'http://' : Security::remove_XSS($urllink),
'title' => Security::remove_XSS($title),
'category_id' => $category,
'on_homepage' => $onhomepage,
'description' => $description,
'target' => $target_link,
];
if ('true' === api_get_setting('search_enabled')) {
$specific_fields = get_specific_field_list();
$form->addCheckBox('index_document', get_lang('Index link title and description?s'), get_lang('Yes'));
foreach ($specific_fields as $specific_field) {
$default_values = '';
if ('editlink' === $action) {
$filter = [
'field_id' => $specific_field['id'],
'ref_id' => intval($_GET['id']),
'tool_id' => '\''.TOOL_LINK.'\'',
];
$values = get_specific_field_values_list($filter, ['value']);
if (!empty($values)) {
$arr_str_values = [];
foreach ($values as $value) {
$arr_str_values[] = $value['value'];
}
$default_values = implode(', ', $arr_str_values);
}
}
$form->addText($specific_field['name'], $specific_field['code']);
$defaults[$specific_field['name']] = $default_values;
}
}
$skillList = SkillModel::addSkillsToForm($form, ITEM_TYPE_LINK, $linkId);
$form->addHidden('lp_id', $lpId);
$form->addButtonSave(get_lang('Save links'), 'submitLink');
$defaults['skills'] = array_keys($skillList);
$form->setDefaults($defaults);
return $form;
}
/**
* @param int $id
* @param string $action
*
* @return FormValidator
*/
public static function getCategoryForm($id, $action)
{
$id = (int) $id;
$action = Security::remove_XSS($action);
$form = new FormValidator(
'category',
'post',
api_get_self().'?action='.$action.'&'.api_get_cidreq()
);
$defaults = [];
if ('addcategory' === $action) {
$form->addHeader(get_lang('Add a category'));
$my_cat_title = get_lang('Add a category');
} else {
$form->addHeader(get_lang('Edit Category'));
$my_cat_title = get_lang('Edit Category');
$defaults = self::getCategory($id);
}
$form->addHidden('id', $id);
$form->addText('category_title', get_lang('Category name'));
$form->addHtmlEditor(
'description',
get_lang('Description'),
false,
false,
['ToolbarSet' => 'Profile', 'Width' => '100%', 'Height' => '130']
);
$form->addButtonSave($my_cat_title, 'submitCategory');
$form->setDefaults($defaults);
return $form;
}
/**
* @param int $id
*
* @return array
*/
public static function getCategory($id)
{
$table = Database::get_course_table(TABLE_LINK_CATEGORY);
$id = (int) $id;
$courseId = api_get_course_int_id();
if (empty($id) || empty($courseId)) {
return [];
}
$sql = "SELECT * FROM $table
WHERE iid = $id";
$result = Database::query($sql);
return Database::fetch_assoc($result);
}
/**
* Move a link up in its category.
*
* @param int $id
*
* @return bool
*/
public static function moveLinkUp($id)
{
return self::moveLinkDisplayOrder($id, 'ASC');
}
/**
* Move a link down in its category.
*
* @param int $id
*
* @return bool
*/
public static function moveLinkDown($id)
{
return self::moveLinkDisplayOrder($id, 'DESC');
}
/**
* @param string $url
*
* @return bool
*/
public static function checkUrl($url)
{
// Check if curl is available.
if (!in_array('curl', get_loaded_extensions())) {
return false;
}
// set URL and other appropriate options
$defaults = [
CURLOPT_URL => $url,
CURLOPT_FOLLOWLOCATION => true, // follow redirects accept youtube.com
CURLOPT_HEADER => 0,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 4,
];
$proxySettings = api_get_setting('platform.proxy_settings', true);
if (!empty($proxySettings) &&
isset($proxySettings['curl_setopt_array'])
) {
$defaults[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
$defaults[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
}
// Create a new cURL resource
$ch = curl_init();
curl_setopt_array($ch, $defaults);
// grab URL and pass it to the browser
ob_start();
$result = curl_exec($ch);
ob_get_clean();
// close cURL resource, and free up system resources
curl_close($ch);
return $result;
}
/**
* Move a link inside its category (display_order field).
*
* @param int $id The link ID
* @param string $direction The direction to sort the links
*
* @return bool
*/
private static function moveLinkDisplayOrder($id, $direction)
{
$em = Database::getManager();
$repo = Container::getLinkRepository();
/** @var CLink $link */
$link = $repo->find($id);
if (!$link) {
return false;
}
$compareLinks = $repo
->findBy(
[
'categoryId' => $link->getCategory() ? $link->getCategory()->getIid() : 0,
],
['displayOrder' => $direction]
);
/** @var CLink $prevLink */
$prevLink = null;
/** @var CLink $compareLink */
foreach ($compareLinks as $compareLink) {
if ($compareLink->getIid() !== $link->getIid()) {
$prevLink = $compareLink;
continue;
}
if (!$prevLink) {
return false;
}
$newPrevLinkDisplayOrder = $link->getDisplayOrder();
$newLinkDisplayOrder = $prevLink->getDisplayOrder();
$link->setDisplayOrder($newLinkDisplayOrder);
$prevLink->setDisplayOrder($newPrevLinkDisplayOrder);
$em->persist($prevLink);
$em->persist($link);
break;
}
$em->flush();
return true;
}
}