Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
integration
prechecker
Commits
4a39dd30
Commit
4a39dd30
authored
Apr 11, 2016
by
Dan Poltawski
Browse files
Merge branch 'wip-MDL-53363-master' of
https://github.com/marinaglancy/moodle
parents
d5327021
4218ad93
Changes
6
Hide whitespace changes
Inline
Side-by-side
course/lib.php
View file @
4a39dd30
...
...
@@ -3917,3 +3917,109 @@ function core_course_inplace_editable($itemtype, $itemid, $newvalue) {
return
\
core_course\output\course_module_name
::
update
(
$itemid
,
$newvalue
);
}
}
/**
* Returns course modules tagged with a specified tag ready for output on tag/index.php page
*
* This is a callback used by the tag area core/course_modules to search for course modules
* tagged with a specific tag.
*
* @param core_tag_tag $tag
* @param bool $exclusivemode if set to true it means that no other entities tagged with this tag
* are displayed on the page and the per-page limit may be bigger
* @param int $fromcontextid context id where the link was displayed, may be used by callbacks
* to display items in the same context first
* @param int $contextid context id where to search for records
* @param bool $recursivecontext search in subcontexts as well
* @param int $page 0-based number of page being displayed
* @return \core_tag\output\tagindex
*/
function
course_get_tagged_course_modules
(
$tag
,
$exclusivemode
=
false
,
$fromcontextid
=
0
,
$contextid
=
0
,
$recursivecontext
=
1
,
$page
=
0
)
{
global
$OUTPUT
;
$perpage
=
$exclusivemode
?
20
:
5
;
// Build select query.
$ctxselect
=
context_helper
::
get_preload_record_columns_sql
(
'ctx'
);
$query
=
"SELECT cm.id AS cmid, c.id AS courseid,
$ctxselect
FROM
{
course_modules
}
cm
JOIN
{
tag_instance
}
tt ON cm.id = tt.itemid
JOIN
{
course
}
c ON cm.course = c.id
JOIN
{
context
}
ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :coursemodulecontextlevel
WHERE tt.itemtype = :itemtype AND tt.tagid = :tagid AND tt.component = :component
AND c.id %COURSEFILTER% AND cm.id %ITEMFILTER%"
;
$params
=
array
(
'itemtype'
=>
'course_modules'
,
'tagid'
=>
$tag
->
id
,
'component'
=>
'core'
,
'coursemodulecontextlevel'
=>
CONTEXT_MODULE
);
if
(
$contextid
)
{
$context
=
context
::
instance_by_id
(
$contextid
);
$query
.
=
$recursivecontext
?
' AND (ctx.id = :contextid OR ctx.path LIKE :path)'
:
' AND ctx.id = :contextid'
;
$params
[
'contextid'
]
=
$context
->
id
;
$params
[
'path'
]
=
$context
->
path
.
'/%'
;
}
$query
.
=
' ORDER BY'
;
if
(
$fromcontextid
)
{
// In order-clause specify that modules from inside "fromctx" context should be returned first.
$fromcontext
=
context
::
instance_by_id
(
$fromcontextid
);
$query
.
=
' (CASE WHEN ctx.id = :fromcontextid OR ctx.path LIKE :frompath THEN 0 ELSE 1 END),'
;
$params
[
'fromcontextid'
]
=
$fromcontext
->
id
;
$params
[
'frompath'
]
=
$fromcontext
->
path
.
'/%'
;
}
$query
.
=
' c.sortorder, cm.id'
;
$totalpages
=
$page
+
1
;
// Use core_tag_index_builder to build and filter the list of items.
// Request one item more than we need so we know if next page exists.
$builder
=
new
core_tag_index_builder
(
'core'
,
'course_modules'
,
$query
,
$params
,
$page
*
$perpage
,
$perpage
+
1
);
while
(
$item
=
$builder
->
has_item_that_needs_access_check
())
{
context_helper
::
preload_from_record
(
$item
);
$courseid
=
$item
->
courseid
;
if
(
!
$builder
->
can_access_course
(
$courseid
))
{
$builder
->
set_accessible
(
$item
,
false
);
continue
;
}
$modinfo
=
get_fast_modinfo
(
$builder
->
get_course
(
$courseid
));
// Set accessibility of this item and all other items in the same course.
$builder
->
walk
(
function
(
$taggeditem
)
use
(
$courseid
,
$modinfo
,
$builder
)
{
if
(
$taggeditem
->
courseid
==
$courseid
)
{
$cm
=
$modinfo
->
get_cm
(
$taggeditem
->
cmid
);
$builder
->
set_accessible
(
$taggeditem
,
$cm
->
uservisible
);
}
});
}
$items
=
$builder
->
get_items
();
if
(
count
(
$items
)
>
$perpage
)
{
$totalpages
=
$page
+
2
;
// We don't need exact page count, just indicate that the next page exists.
array_pop
(
$items
);
}
// Build the display contents.
if
(
$items
)
{
$tagfeed
=
new
core_tag\output\tagfeed
();
foreach
(
$items
as
$item
)
{
context_helper
::
preload_from_record
(
$item
);
$course
=
$builder
->
get_course
(
$item
->
courseid
);
$modinfo
=
get_fast_modinfo
(
$course
);
$cm
=
$modinfo
->
get_cm
(
$item
->
cmid
);
$courseurl
=
course_get_url
(
$item
->
courseid
,
$cm
->
sectionnum
);
$cmname
=
$cm
->
get_formatted_name
();
if
(
!
$exclusivemode
)
{
$cmname
=
shorten_text
(
$cmname
,
100
);
}
$cmname
=
html_writer
::
link
(
$cm
->
url
?:
$courseurl
,
$cmname
);
$coursename
=
format_string
(
$course
->
fullname
,
true
,
array
(
'context'
=>
context_course
::
instance
(
$item
->
courseid
)));
$coursename
=
html_writer
::
link
(
$courseurl
,
$coursename
);
$icon
=
html_writer
::
empty_tag
(
'img'
,
array
(
'src'
=>
$cm
->
get_icon_url
()));
$tagfeed
->
add
(
$icon
,
$cmname
,
$coursename
);
}
$content
=
$OUTPUT
->
render_from_template
(
'core_tag/tagfeed'
,
$tagfeed
->
export_for_template
(
$OUTPUT
));
return
new
core_tag\output\tagindex
(
$tag
,
'core'
,
'course_modules'
,
$content
,
$exclusivemode
,
$fromcontextid
,
$contextid
,
$recursivecontext
,
$page
,
$totalpages
);
}
}
course/tests/courselib_test.php
View file @
4a39dd30
...
...
@@ -2809,4 +2809,117 @@ class core_course_courselib_testcase extends advanced_testcase {
$this
->
assertEquals
(
'New forum name'
,
$res
[
'value'
]);
$this
->
assertEquals
(
'New forum name'
,
$DB
->
get_field
(
'forum'
,
'name'
,
array
(
'id'
=>
$forum
->
id
)));
}
/**
* Testing function course_get_tagged_course_modules - search tagged course modules
*/
public
function
test_course_get_tagged_course_modules
()
{
global
$DB
;
$this
->
resetAfterTest
();
$course3
=
$this
->
getDataGenerator
()
->
create_course
();
$course2
=
$this
->
getDataGenerator
()
->
create_course
();
$course1
=
$this
->
getDataGenerator
()
->
create_course
();
$cm11
=
$this
->
getDataGenerator
()
->
create_module
(
'assign'
,
array
(
'course'
=>
$course1
->
id
,
'tags'
=>
'Cat, Dog'
));
$cm12
=
$this
->
getDataGenerator
()
->
create_module
(
'page'
,
array
(
'course'
=>
$course1
->
id
,
'tags'
=>
'Cat, Mouse'
,
'visible'
=>
0
));
$cm13
=
$this
->
getDataGenerator
()
->
create_module
(
'page'
,
array
(
'course'
=>
$course1
->
id
,
'tags'
=>
'Cat, Mouse, Dog'
));
$cm21
=
$this
->
getDataGenerator
()
->
create_module
(
'forum'
,
array
(
'course'
=>
$course2
->
id
,
'tags'
=>
'Cat, Mouse'
));
$cm31
=
$this
->
getDataGenerator
()
->
create_module
(
'forum'
,
array
(
'course'
=>
$course3
->
id
,
'tags'
=>
'Cat, Mouse'
));
// Admin is able to view everything.
$this
->
setAdminUser
();
$res
=
course_get_tagged_course_modules
(
core_tag_tag
::
get_by_name
(
0
,
'Cat'
),
/*$exclusivemode = */
false
,
/*$fromctx = */
0
,
/*$ctx = */
0
,
/*$rec = */
1
,
/*$page = */
0
);
$this
->
assertRegExp
(
'/'
.
$cm11
->
name
.
'/'
,
$res
->
content
);
$this
->
assertRegExp
(
'/'
.
$cm12
->
name
.
'/'
,
$res
->
content
);
$this
->
assertRegExp
(
'/'
.
$cm13
->
name
.
'/'
,
$res
->
content
);
$this
->
assertRegExp
(
'/'
.
$cm21
->
name
.
'/'
,
$res
->
content
);
$this
->
assertRegExp
(
'/'
.
$cm31
->
name
.
'/'
,
$res
->
content
);
// Results from course1 are returned before results from course2.
$this
->
assertTrue
(
strpos
(
$res
->
content
,
$cm11
->
name
)
<
strpos
(
$res
->
content
,
$cm21
->
name
));
// Ordinary user is not able to see anything.
$user
=
$this
->
getDataGenerator
()
->
create_user
();
$this
->
setUser
(
$user
);
$res
=
course_get_tagged_course_modules
(
core_tag_tag
::
get_by_name
(
0
,
'Cat'
),
/*$exclusivemode = */
false
,
/*$fromctx = */
0
,
/*$ctx = */
0
,
/*$rec = */
1
,
/*$page = */
0
);
$this
->
assertNull
(
$res
);
// Enrol user as student in course1 and course2.
$roleids
=
$DB
->
get_records_menu
(
'role'
,
null
,
''
,
'shortname, id'
);
$this
->
getDataGenerator
()
->
enrol_user
(
$user
->
id
,
$course1
->
id
,
$roleids
[
'student'
]);
$this
->
getDataGenerator
()
->
enrol_user
(
$user
->
id
,
$course2
->
id
,
$roleids
[
'student'
]);
core_tag_index_builder
::
reset_caches
();
// Searching in the course context returns visible modules in this course.
$context
=
context_course
::
instance
(
$course1
->
id
);
$res
=
course_get_tagged_course_modules
(
core_tag_tag
::
get_by_name
(
0
,
'Cat'
),
/*$exclusivemode = */
false
,
/*$fromctx = */
0
,
/*$ctx = */
$context
->
id
,
/*$rec = */
1
,
/*$page = */
0
);
$this
->
assertRegExp
(
'/'
.
$cm11
->
name
.
'/'
,
$res
->
content
);
$this
->
assertNotRegExp
(
'/'
.
$cm12
->
name
.
'/'
,
$res
->
content
);
$this
->
assertRegExp
(
'/'
.
$cm13
->
name
.
'/'
,
$res
->
content
);
$this
->
assertNotRegExp
(
'/'
.
$cm21
->
name
.
'/'
,
$res
->
content
);
$this
->
assertNotRegExp
(
'/'
.
$cm31
->
name
.
'/'
,
$res
->
content
);
// Searching FROM the course context returns visible modules in all courses.
$context
=
context_course
::
instance
(
$course2
->
id
);
$res
=
course_get_tagged_course_modules
(
core_tag_tag
::
get_by_name
(
0
,
'Cat'
),
/*$exclusivemode = */
false
,
/*$fromctx = */
$context
->
id
,
/*$ctx = */
0
,
/*$rec = */
1
,
/*$page = */
0
);
$this
->
assertRegExp
(
'/'
.
$cm11
->
name
.
'/'
,
$res
->
content
);
$this
->
assertNotRegExp
(
'/'
.
$cm12
->
name
.
'/'
,
$res
->
content
);
$this
->
assertRegExp
(
'/'
.
$cm13
->
name
.
'/'
,
$res
->
content
);
$this
->
assertRegExp
(
'/'
.
$cm21
->
name
.
'/'
,
$res
->
content
);
$this
->
assertNotRegExp
(
'/'
.
$cm31
->
name
.
'/'
,
$res
->
content
);
// No access to course3.
// Results from course2 are returned before results from course1.
$this
->
assertTrue
(
strpos
(
$res
->
content
,
$cm21
->
name
)
<
strpos
(
$res
->
content
,
$cm11
->
name
));
// Enrol user in course1 as a teacher - now he should be able to see hidden module.
$this
->
getDataGenerator
()
->
enrol_user
(
$user
->
id
,
$course1
->
id
,
$roleids
[
'editingteacher'
]);
get_fast_modinfo
(
0
,
0
,
true
);
$context
=
context_course
::
instance
(
$course1
->
id
);
$res
=
course_get_tagged_course_modules
(
core_tag_tag
::
get_by_name
(
0
,
'Cat'
),
/*$exclusivemode = */
false
,
/*$fromctx = */
$context
->
id
,
/*$ctx = */
0
,
/*$rec = */
1
,
/*$page = */
0
);
$this
->
assertRegExp
(
'/'
.
$cm12
->
name
.
'/'
,
$res
->
content
);
// Create more modules and try pagination.
$cm14
=
$this
->
getDataGenerator
()
->
create_module
(
'assign'
,
array
(
'course'
=>
$course1
->
id
,
'tags'
=>
'Cat, Dog'
));
$cm15
=
$this
->
getDataGenerator
()
->
create_module
(
'page'
,
array
(
'course'
=>
$course1
->
id
,
'tags'
=>
'Cat, Mouse'
,
'visible'
=>
0
));
$cm16
=
$this
->
getDataGenerator
()
->
create_module
(
'page'
,
array
(
'course'
=>
$course1
->
id
,
'tags'
=>
'Cat, Mouse, Dog'
));
$context
=
context_course
::
instance
(
$course1
->
id
);
$res
=
course_get_tagged_course_modules
(
core_tag_tag
::
get_by_name
(
0
,
'Cat'
),
/*$exclusivemode = */
false
,
/*$fromctx = */
0
,
/*$ctx = */
$context
->
id
,
/*$rec = */
1
,
/*$page = */
0
);
$this
->
assertRegExp
(
'/'
.
$cm11
->
name
.
'/'
,
$res
->
content
);
$this
->
assertRegExp
(
'/'
.
$cm12
->
name
.
'/'
,
$res
->
content
);
$this
->
assertRegExp
(
'/'
.
$cm13
->
name
.
'/'
,
$res
->
content
);
$this
->
assertNotRegExp
(
'/'
.
$cm21
->
name
.
'/'
,
$res
->
content
);
$this
->
assertRegExp
(
'/'
.
$cm14
->
name
.
'/'
,
$res
->
content
);
$this
->
assertRegExp
(
'/'
.
$cm15
->
name
.
'/'
,
$res
->
content
);
$this
->
assertNotRegExp
(
'/'
.
$cm16
->
name
.
'/'
,
$res
->
content
);
$this
->
assertNotRegExp
(
'/'
.
$cm31
->
name
.
'/'
,
$res
->
content
);
// No access to course3.
$this
->
assertEmpty
(
$res
->
prevpageurl
);
$this
->
assertNotEmpty
(
$res
->
nextpageurl
);
$res
=
course_get_tagged_course_modules
(
core_tag_tag
::
get_by_name
(
0
,
'Cat'
),
/*$exclusivemode = */
false
,
/*$fromctx = */
0
,
/*$ctx = */
$context
->
id
,
/*$rec = */
1
,
/*$page = */
1
);
$this
->
assertNotRegExp
(
'/'
.
$cm11
->
name
.
'/'
,
$res
->
content
);
$this
->
assertNotRegExp
(
'/'
.
$cm12
->
name
.
'/'
,
$res
->
content
);
$this
->
assertNotRegExp
(
'/'
.
$cm13
->
name
.
'/'
,
$res
->
content
);
$this
->
assertNotRegExp
(
'/'
.
$cm21
->
name
.
'/'
,
$res
->
content
);
$this
->
assertNotRegExp
(
'/'
.
$cm14
->
name
.
'/'
,
$res
->
content
);
$this
->
assertNotRegExp
(
'/'
.
$cm15
->
name
.
'/'
,
$res
->
content
);
$this
->
assertRegExp
(
'/'
.
$cm16
->
name
.
'/'
,
$res
->
content
);
$this
->
assertNotRegExp
(
'/'
.
$cm31
->
name
.
'/'
,
$res
->
content
);
// No access to course3.
$this
->
assertNotEmpty
(
$res
->
prevpageurl
);
$this
->
assertEmpty
(
$res
->
nextpageurl
);
}
}
lang/en/cache.php
View file @
4a39dd30
...
...
@@ -57,6 +57,7 @@ $string['cachedef_navigation_expandcourse'] = 'Navigation expandable courses';
$string
[
'cachedef_observers'
]
=
'Event observers'
;
$string
[
'cachedef_plugin_functions'
]
=
'Plugins available callbacks'
;
$string
[
'cachedef_plugin_manager'
]
=
'Plugin info manager'
;
$string
[
'cachedef_tagindexbuilder'
]
=
'Search results for tagged items'
;
$string
[
'cachedef_questiondata'
]
=
'Question definitions'
;
$string
[
'cachedef_repositories'
]
=
'Repositories instances data'
;
$string
[
'cachedef_grade_categories'
]
=
'Grade category queries'
;
...
...
lib/db/caches.php
View file @
4a39dd30
...
...
@@ -277,5 +277,18 @@ $definitions = array(
'mode'
=>
cache_store
::
MODE_REQUEST
,
'simplekeys'
=>
true
,
'simpledata'
=>
true
)
),
// Caches tag index builder results.
'tagindexbuilder'
=>
array
(
'mode'
=>
cache_store
::
MODE_SESSION
,
'simplekeys'
=>
true
,
'simplevalues'
=>
true
,
'staticacceleration'
=>
true
,
'staticaccelerationsize'
=>
10
,
'ttl'
=>
900
,
// 15 minutes.
'invalidationevents'
=>
array
(
'resettagindexbuilder'
,
),
),
);
lib/db/tag.php
View file @
4a39dd30
...
...
@@ -83,5 +83,7 @@ $tagareas = array(
array
(
'itemtype'
=>
'course_modules'
,
// Course modules.
'component'
=>
'core'
,
'callback'
=>
'course_get_tagged_course_modules'
,
'callbackfile'
=>
'/course/lib.php'
,
),
);
tag/classes/index_builder.php
0 → 100644
View file @
4a39dd30
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class core_tag_index_builder
*
* @package core_tag
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined
(
'MOODLE_INTERNAL'
)
||
die
();
/**
* Helper to build tag index
*
* This can be used by components to implement tag area callbacks. This is especially
* useful for in-course content when we need to check and cache user's access to
* multiple courses. Course access and accessible items are stored in session cache
* with 15 minutes expiry time.
*
* Example of usage:
*
* $builder = new core_tag_index_builder($component, $itemtype, $sql, $params, $from, $limit);
* while ($item = $builder->has_item_that_needs_access_check()) {
* if (!$builder->can_access_course($item->courseid)) {
* $builder->set_accessible($item, false);
* } else {
* $accessible = true; // Check access and set $accessible respectively.
* $builder->set_accessible($item, $accessible);
* }
* }
* $items = $builder->get_items();
*
* @package core_tag
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class
core_tag_index_builder
{
/** @var string component specified in the constructor */
protected
$component
;
/** @var string itemtype specified in the constructor */
protected
$itemtype
;
/** @var string SQL statement */
protected
$sql
;
/** @var array parameters for SQL statement */
protected
$params
;
/** @var int index from which to return records */
protected
$from
;
/** @var int maximum number of records to return */
protected
$limit
;
/** @var array result of SQL query */
protected
$items
;
/** @var array list of item ids ( array_keys($this->items) ) */
protected
$itemkeys
;
/** @var string alias of the item id in the SQL result */
protected
$idfield
=
'id'
;
/** @var array cache of items accessibility (id => bool) */
protected
$accessibleitems
;
/** @var array cache of courses accessibility (courseid => bool) */
protected
$courseaccess
;
/** @var bool indicates that items cache was changed in this class and needs pushing to MUC */
protected
$cachechangedaccessible
=
false
;
/** @var bool indicates that course accessibiity cache was changed in this class and needs pushing to MUC */
protected
$cachechangedcourse
=
false
;
/** @var array cached courses (not pushed to MUC) */
protected
$courses
;
/**
* Constructor.
*
* Specify the SQL query for retrieving the tagged items, SQL query must:
* - return the item id as the first field and make sure that it is unique in the result
* - provide ORDER BY that exclude any possibility of random results, if $fromctx was specified when searching
* for tagged items it is the best practice to make sure that items from this context are returned first.
*
* This query may also contain placeholders %COURSEFILTER% or %ITEMFILTER% that will be substituted with
* expressions excluding courses and/or filters that are already known as inaccessible.
*
* Example: "WHERE c.id %COURSEFILTER% AND cm.id %ITEMFILTER%"
*
* This query may contain fields to preload context if context is needed for formatting values.
*
* It is recommended to sort by course sortorder first, this way the items from the same course will be next to
* each other and the sequence of courses will the same in different tag areas.
*
* @param string $component component responsible for tagging
* @param string $itemtype type of item that is being tagged
* @param string $sql SQL query that would retrieve all relevant items without permission check
* @param array $params parameters for the query (must be named)
* @param int $from return a subset of records, starting at this point
* @param int $limit return a subset comprising this many records in total (this field is NOT optional)
*/
public
function
__construct
(
$component
,
$itemtype
,
$sql
,
$params
,
$from
,
$limit
)
{
$this
->
component
=
preg_replace
(
'/[^A-Za-z0-9_]/i'
,
''
,
$component
);
$this
->
itemtype
=
preg_replace
(
'/[^A-Za-z0-9_]/i'
,
''
,
$itemtype
);
$this
->
sql
=
$sql
;
$this
->
params
=
$params
;
$this
->
from
=
$from
;
$this
->
limit
=
$limit
;
$this
->
courses
=
array
();
}
/**
* Substitute %COURSEFILTER% with an expression filtering out courses where current user does not have access
*/
protected
function
prepare_sql_courses
()
{
global
$DB
;
if
(
!
preg_match
(
'/\\%COURSEFILTER\\%/'
,
$this
->
sql
))
{
return
;
}
$this
->
init_course_access
();
$unaccessiblecourses
=
array_filter
(
$this
->
courseaccess
,
function
(
$item
)
{
return
!
$item
;
});
$idx
=
0
;
while
(
preg_match
(
'/^([^\\0]*?)\\%COURSEFILTER\\%([^\\0]*)$/'
,
$this
->
sql
,
$matches
))
{
list
(
$sql
,
$params
)
=
$DB
->
get_in_or_equal
(
array_keys
(
$unaccessiblecourses
),
SQL_PARAMS_NAMED
,
'ca_'
.
(
$idx
++
)
.
'_'
,
false
,
0
);
$this
->
sql
=
$matches
[
1
]
.
' '
.
$sql
.
' '
.
$matches
[
2
];
$this
->
params
+=
$params
;
}
}
/**
* Substitute %ITEMFILTER% with an expression filtering out items where current user does not have access
*/
protected
function
prepare_sql_items
()
{
global
$DB
;
if
(
!
preg_match
(
'/\\%ITEMFILTER\\%/'
,
$this
->
sql
))
{
return
;
}
$this
->
init_items_access
();
$unaccessibleitems
=
array_filter
(
$this
->
accessibleitems
,
function
(
$item
)
{
return
!
$item
;
});
$idx
=
0
;
while
(
preg_match
(
'/^([^\\0]*?)\\%ITEMFILTER\\%([^\\0]*)$/'
,
$this
->
sql
,
$matches
))
{
list
(
$sql
,
$params
)
=
$DB
->
get_in_or_equal
(
array_keys
(
$unaccessibleitems
),
SQL_PARAMS_NAMED
,
'ia_'
.
(
$idx
++
)
.
'_'
,
false
,
0
);
$this
->
sql
=
$matches
[
1
]
.
' '
.
$sql
.
' '
.
$matches
[
2
];
$this
->
params
+=
$params
;
}
}
/**
* Ensures that SQL query was executed and $this->items is filled
*/
protected
function
retrieve_items
()
{
global
$DB
;
if
(
$this
->
items
!==
null
)
{
return
;
}
$this
->
prepare_sql_courses
();
$this
->
prepare_sql_items
();
$this
->
items
=
$DB
->
get_records_sql
(
$this
->
sql
,
$this
->
params
);
$this
->
itemkeys
=
array_keys
(
$this
->
items
);
if
(
$this
->
items
)
{
// Find the name of the first key of the item - usually 'id' but can be something different.
// This must be a unique identifier of the item.
$firstitem
=
reset
(
$this
->
items
);
$firstitemarray
=
(
array
)
$firstitem
;
$this
->
idfield
=
key
(
$firstitemarray
);
}
}
/**
* Returns the filtered records from SQL query result.
*
* This function can only be executed after $builder->has_item_that_needs_access_check() returns null
*
*
* @return array
*/
public
function
get_items
()
{
global
$DB
,
$CFG
;
if
(
is_siteadmin
())
{
$this
->
sql
=
preg_replace
(
'/\\%COURSEFILTER\\%/'
,
'<>0'
,
$this
->
sql
);
$this
->
sql
=
preg_replace
(
'/\\%ITEMFILTER\\%/'
,
'<>0'
,
$this
->
sql
);
return
$DB
->
get_records_sql
(
$this
->
sql
,
$this
->
params
,
$this
->
from
,
$this
->
limit
);
}
if
(
$CFG
->
debugdeveloper
&&
$this
->
has_item_that_needs_access_check
())
{
debugging
(
'Caller must ensure that has_item_that_needs_access_check() does not return anything '
.
'before calling get_items(). The item list may be incomplete'
,
DEBUG_DEVELOPER
);
}
$this
->
retrieve_items
();
$this
->
save_caches
();
$idx
=
0
;
$items
=
array
();
foreach
(
$this
->
itemkeys
as
$id
)
{
if
(
!
array_key_exists
(
$id
,
$this
->
accessibleitems
)
||
!
$this
->
accessibleitems
[
$id
])
{
continue
;
}
if
(
$idx
>=
$this
->
from
)
{
$items
[
$id
]
=
$this
->
items
[
$id
];
}
$idx
++
;
if
(
$idx
>=
$this
->
from
+
$this
->
limit
)
{
break
;
}
}
return
$items
;
}
/**
* Returns the first row from the SQL result that we don't know whether it is accessible by user or not.
*
* This will return null when we have necessary number of accessible items to return in {@link get_items()}
*
* After analyzing you may decide to mark not only this record but all similar as accessible or not accessible.
* For example, if you already call get_fast_modinfo() to check this item's accessibility, why not mark all
* items in the same course as accessible or not accessible.
*
* Helpful methods: {@link set_accessible()} and {@link walk()}
*
* @return null|object
*/
public
function
has_item_that_needs_access_check
()
{
if
(
is_siteadmin
())
{
return
null
;
}
$this
->
retrieve_items
();
$counter
=
0
;
// Counter for accessible items.
foreach
(
$this
->
itemkeys
as
$id
)
{
if
(
!
array_key_exists
(
$id
,
$this
->
accessibleitems
))
{
return
(
object
)(
array
)
$this
->
items
[
$id
];
}
$counter
+=
$this
->
accessibleitems
[
$id
]
?
1
:
0
;
if
(
$counter
>=
$this
->
from
+
$this
->
limit
)
{
// We found enough accessible items fot get_items() method, do not look any further.
return
null
;
}
}
return
null
;
}
/**
* Walk through the array of items and call $callable for each of them
* @param callable $callable
*/
public
function
walk
(
$callable
)
{
$this
->
retrieve_items
();
array_walk
(
$this
->
items
,
$callable
);
}
/**
* Marks record or group of records as accessible (or not accessible)
*
* @param int|std_Class $identifier either record id of the item that needs to be set accessible
* @param bool $accessible whether to mark as accessible or not accessible (default true)
*/
public
function
set_accessible
(
$identifier
,
$accessible
=
true
)
{
if
(
is_object
(
$identifier
))
{
$identifier
=
(
int
)(
$identifier
->
{
$this
->
idfield
});
}
$this
->
init_items_access
();
if
(
is_int
(
$identifier
))
{
$accessible
=
(
int
)(
bool
)
$accessible
;
if
(
!
array_key_exists
(
$identifier
,
$this
->
accessibleitems
)
||
$this
->
accessibleitems
[
$identifier
]
!=
$accessible
)
{
$this
->
accessibleitems
[
$identifier
]
=
$accessible
;
$this
->
cachechangedaccessible
;
}
}
else
{
throw
new
coding_exception
(
'Argument $identifier must be either int or object'
);
}
}
/**
* Retrieves a course record (only fields id,visible,fullname,shortname,cacherev).
*
* This method is useful because it also caches results and preloads course context.
*
* @param int $courseid
*/
public
function
get_course
(
$courseid
)
{
global
$DB
;
if
(
!
array_key_exists
(
$courseid
,
$this
->
courses
))
{
$ctxquery
=
context_helper
::
get_preload_record_columns_sql
(
'ctx'
);
$sql
=
"SELECT c.id,c.visible,c.fullname,c.shortname,c.cacherev,
$ctxquery
FROM
{
course
}
c JOIN
{
context
}
ctx ON ctx.contextlevel = ? AND ctx.instanceid=c.id
WHERE c.id = ?"
;
$params
=
array
(
CONTEXT_COURSE
,
$courseid
);
$this
->
courses
[
$courseid
]
=
$DB
->
get_record_sql
(
$sql
,
$params
);
context_helper
::
preload_from_record
(
$this
->
courses
[
$courseid
]);
}
return
$this
->
courses
[
$courseid
];
}
/**
* Ensures that we read the course access from the cache.
*/
protected
function
init_course_access
()
{
if
(
$this
->
courseaccess
===
null
)
{
$this
->
courseaccess
=
cache
::
make
(
'core'
,
'tagindexbuilder'
)
->
get
(
'courseaccess'
)
?:
[];
}
}
/**
* Ensures that we read the items access from the cache.
*/
protected
function
init_items_access
()
{
if
(
$this
->
accessibleitems
===
null
)
{
$this
->
accessibleitems
=
cache
::
make
(
'core'
,
'tagindexbuilder'
)
->
get
(
$this
->
component
.
'__'
.
$this
->
itemtype
)
?:
[];
}
}
/**