22
33class HookCallbacks
44{
5+ private const REVIEWER_INTEREST_FILTER_PARAM_PREFIX = 'interestOption__ ' ;
6+
57 private $ plugin ;
68
79 public function __construct ($ plugin )
@@ -80,6 +82,23 @@ public function requestMessageFilter($output, $templateMgr)
8082 return $ output ;
8183 }
8284
85+ public function reviewerInterestFilterMarkupFilter ($ output , $ templateMgr )
86+ {
87+ $ pattern = '/<script type="text\/javascript">\s*pkp\.registry\.init\( \'select-reviewer-[^ \']+ \',\s* \'Container \',\s*\{.*?<\/script>/s ' ;
88+ if (preg_match ($ pattern , $ output , $ matches , PREG_OFFSET_CAPTURE )) {
89+ $ offset = $ matches [0 ][1 ];
90+
91+ $ newOutput = substr ($ output , 0 , $ offset );
92+ $ newOutput .= $ this ->getReviewerInterestFilterComponentScriptTag ();
93+ $ newOutput .= substr ($ output , $ offset );
94+
95+ $ output = $ newOutput ;
96+ $ templateMgr ->unregisterFilter ('output ' , [$ this , 'reviewerInterestFilterMarkupFilter ' ]);
97+ }
98+
99+ return $ output ;
100+ }
101+
83102 public function registrationInterestsFilter ($ output , $ templateMgr )
84103 {
85104 $ pattern = '/<div\s+id="reviewerInterests"[^>]*>.*?<\/div>/s ' ;
@@ -101,10 +120,99 @@ public function setupOptionsConfigurationGridHandler(string $hookName, array $pa
101120 return false ;
102121 }
103122
123+ public function addReviewerInterestFilterOnFormDisplay (string $ hookName , array $ params )
124+ {
125+ $ request = Application::get ()->getRequest ();
126+ $ context = $ request ->getContext ();
127+ if (!$ context ) {
128+ return false ;
129+ }
130+
131+ $ templateMgr = TemplateManager::getManager ($ request );
132+ $ this ->addReviewerInterestFilter ($ templateMgr , $ context ->getId ());
133+
134+ return false ;
135+ }
136+
137+ public function addReviewerInterestFilterParam (string $ hookName , array $ params )
138+ {
139+ $ reviewerParams = &$ params [0 ];
140+ $ slimRequest = $ params [1 ];
141+
142+ $ request = Application::get ()->getRequest ();
143+ $ context = $ request ->getContext ();
144+ if (!$ context ) {
145+ return false ;
146+ }
147+
148+ $ availableOptions = $ this ->getInterestOptions ($ context ->getId ());
149+ $ selectedOptions = [];
150+ foreach ($ slimRequest ->getQueryParams () as $ param => $ value ) {
151+ if (strpos ($ param , self ::REVIEWER_INTEREST_FILTER_PARAM_PREFIX ) !== 0 ) {
152+ continue ;
153+ }
154+
155+ if (is_string ($ value )) {
156+ $ selectedOptions [] = $ value ;
157+ }
158+ }
159+
160+ $ validatedOptions = $ this ->normalizeSelectedInterestOptions ($ selectedOptions , $ availableOptions );
161+ if (empty ($ validatedOptions )) {
162+ return false ;
163+ }
164+
165+ $ reviewerParams ['interestOption ' ] = $ validatedOptions ;
166+
167+ return false ;
168+ }
169+
170+ public function setReviewerInterestFilter (string $ hookName , array $ params )
171+ {
172+ $ reviewerQueryBuilder = $ params [0 ];
173+ $ args = $ params [1 ];
174+
175+ if (empty ($ args ['interestOption ' ]) || (!is_string ($ args ['interestOption ' ]) && !is_array ($ args ['interestOption ' ]))) {
176+ return false ;
177+ }
178+
179+ $ interestOptions = $ this ->normalizeSelectedInterestOptions ($ args ['interestOption ' ]);
180+ if (empty ($ interestOptions )) {
181+ return false ;
182+ }
183+
184+ $ reviewerQueryBuilder ->selectionOfReviewingInterestsInterestOptions = $ interestOptions ;
185+
186+ return false ;
187+ }
188+
189+ public function applyReviewerInterestFilter (string $ hookName , array $ params )
190+ {
191+ $ query = $ params [0 ];
192+ $ userQueryBuilder = $ params [1 ];
193+
194+ if (empty ($ userQueryBuilder ->selectionOfReviewingInterestsInterestOptions )) {
195+ return false ;
196+ }
197+
198+ $ interestOptions = $ userQueryBuilder ->selectionOfReviewingInterestsInterestOptions ;
199+ $ normalizedInterestOptions = array_map ('mb_strtolower ' , $ interestOptions );
200+ $ placeholders = implode (', ' , array_fill (0 , count ($ normalizedInterestOptions ), '? ' ));
201+
202+ $ query ->whereExists (function ($ query ) use ($ normalizedInterestOptions , $ placeholders ) {
203+ $ query ->from ('user_interests ' , 'ui ' )
204+ ->join ('controlled_vocab_entry_settings AS cves ' , 'ui.controlled_vocab_entry_id ' , '= ' , 'cves.controlled_vocab_entry_id ' )
205+ ->whereColumn ('ui.user_id ' , '= ' , 'u.user_id ' )
206+ ->where ('cves.setting_name ' , '= ' , 'interest ' )
207+ ->whereRaw ('LOWER(cves.setting_value) IN ( ' . $ placeholders . ') ' , $ normalizedInterestOptions );
208+ });
209+
210+ return false ;
211+ }
212+
104213 private function addInterestsScripts ($ templateMgr , $ contextId )
105214 {
106- $ options = $ this ->plugin ->getSetting ($ contextId , 'interestOptions ' ) ?: array ();
107- $ optionsArray = array_values ($ options );
215+ $ optionsArray = $ this ->getInterestOptions ($ contextId );
108216
109217 $ inlineScript = '$.pkp.plugins.generic = $.pkp.plugins.generic || {}; ' ;
110218 $ inlineScript .= '$.pkp.plugins.generic.selectionOfReviewingInterests = ' ;
@@ -149,4 +257,82 @@ private function addRegistrationFilter($templateMgr)
149257 [$ this , 'registrationInterestsFilter ' ]
150258 );
151259 }
260+
261+ private function addReviewerInterestFilter ($ templateMgr , $ contextId )
262+ {
263+ $ selectReviewerListData = $ templateMgr ->getTemplateVars ('selectReviewerListData ' );
264+ if (empty ($ selectReviewerListData ['components ' ]['selectReviewer ' ])) {
265+ return ;
266+ }
267+
268+ $ interestOptions = $ this ->getInterestOptions ($ contextId );
269+ if (empty ($ interestOptions )) {
270+ return ;
271+ }
272+
273+ $ this ->addReviewerInterestFilterMarkup ($ templateMgr );
274+
275+ $ filters = $ selectReviewerListData ['components ' ]['selectReviewer ' ]['filters ' ] ?? [];
276+ foreach ($ interestOptions as $ index => $ interestOption ) {
277+ $ filters [] = [
278+ 'param ' => self ::REVIEWER_INTEREST_FILTER_PARAM_PREFIX . $ index ,
279+ 'value ' => $ interestOption ,
280+ 'title ' => $ interestOption ,
281+ 'groupTitle ' => 'Filter by interest area ' ,
282+ 'showGroupTitle ' => $ index === 0 ,
283+ 'filterType ' => 'reviewer-interest-filter ' ,
284+ ];
285+ }
286+
287+ $ selectReviewerListData ['components ' ]['selectReviewer ' ]['filters ' ] = $ filters ;
288+ $ templateMgr ->assign ('selectReviewerListData ' , $ selectReviewerListData );
289+ }
290+
291+ private function addReviewerInterestFilterMarkup ($ templateMgr )
292+ {
293+ $ templateMgr ->registerFilter (
294+ 'output ' ,
295+ [$ this , 'reviewerInterestFilterMarkupFilter ' ]
296+ );
297+ }
298+
299+ private function getReviewerInterestFilterComponentScriptTag ()
300+ {
301+ $ request = Application::get ()->getRequest ();
302+ $ scriptUrl = $ request ->getBaseUrl () . '/ ' . $ this ->plugin ->getPluginPath () . '/js/reviewerInterestFilter.js ' ;
303+
304+ return '<script type="text/javascript" src=" ' . $ scriptUrl . '"></script> ' ;
305+ }
306+
307+ private function getInterestOptions ($ contextId )
308+ {
309+ $ options = $ this ->plugin ->getSetting ($ contextId , 'interestOptions ' ) ?: array ();
310+ $ options = array_map ('trim ' , array_values ($ options ));
311+
312+ return array_values (array_filter ($ options , function ($ option ) {
313+ return $ option !== '' ;
314+ }));
315+ }
316+
317+ private function normalizeSelectedInterestOptions ($ interestOptions , $ availableOptions = null )
318+ {
319+ if (is_string ($ interestOptions )) {
320+ $ interestOptions = [$ interestOptions ];
321+ } elseif (!is_array ($ interestOptions )) {
322+ return [];
323+ }
324+
325+ $ interestOptions = array_map ('trim ' , $ interestOptions );
326+ $ interestOptions = array_values (array_unique (array_filter ($ interestOptions , function ($ option ) {
327+ return is_string ($ option ) && $ option !== '' ;
328+ })));
329+
330+ if ($ availableOptions === null ) {
331+ return $ interestOptions ;
332+ }
333+
334+ return array_values (array_filter ($ interestOptions , function ($ option ) use ($ availableOptions ) {
335+ return in_array ($ option , $ availableOptions , true );
336+ }));
337+ }
152338}
0 commit comments