Search in ACP

Looking for a MOD? Have a MOD request? Post here for help. (Note: This forum is community supported; phpBB does not have official MOD authors)
Anti-Spam Guide
User avatar
AmigoJack
Registered User
Posts: 6127
Joined: Tue Jun 15, 2010 11:33 am
Location: グリーン ヒル ゾーン

Re: Search in ACP

Post by AmigoJack »

Renji wrote:Can I use this as my signature ?
Sure. Royalty free. :P
  • "The problem is probably not my English but you do not want to understand correctly. ... We will not come anybody anyway, nevertheless, it's best to shit this." Affin, 2018-11-20
  • "But this shit is not here for you. You can follow with your. Maybe the question, instead, was for you, who know, so you shoved us how you are." axe70, 2020-10-10
  • "My reaction is not to everyone, especially to you." Raptiye, 2021-02-28
User avatar
AmigoJack
Registered User
Posts: 6127
Joined: Tue Jun 15, 2010 11:33 am
Location: グリーン ヒル ゾーン

Re: Search in ACP

Post by AmigoJack »

Got a prototype where you can type in keywords to find appropriate sections within the ACP. Not a full "live" search in the means of AJAX.

Open /adm/index.php and find:

Code: Select all

// the acp template is never stored in the database
$user->theme['template_storedb'] = false; 
After, add:

Code: Select all

/*** 2012-08-23 BEGIN AmigoJack
    Provide a search to find captions within the ACP. ***/
$bLiveSearch= request_var( 'livesearch', '' )== 'livesearch';
if( $bLiveSearch ) {
    $sKeyword= htmlspecialchars_decode( request_var( 'keywords', '', TRUE ) );  // Convert HTML entities
    $aKeyword= preg_split( '#[\\s,]+#', $sKeyword, -1, PREG_SPLIT_NO_EMPTY );  // Separate keywords by whitespace and comma

    // Keywords enclosed in quotes must be re-concatinated
    $aCombine= array();
    $bCombine= FALSE;
    foreach( $aKeyword as $iWord=> $sWord ) {
        if( utf8_substr( $sWord, 0, 1 )== '"' ) {  // Begin of a word group
            $bCombine= TRUE;
            $aKeyword[$iWord]= trim( utf8_substr( $sWord, 1 ) );  // Remove quotes character
        }
        if( $bCombine ) $aCombine[]= $iWord;  // Collect keyword if within a word group
        if( utf8_substr( $sWord, -1, 1 )== '"' ) {  // End of a word group
            $bCombine= FALSE;
            $aKeyword[$iWord]= trim( utf8_substr( $sWord, 0, -1 ) );  // Remove quotes character

            $iFirst= $aCombine[0];  // Replace first keyword with a concatination of all others
            unset( $aCombine[0] );

            foreach( $aCombine as $iCombine ) {
                $aKeyword[$iFirst].= ' '. $aKeyword[$iCombine];
                unset( $aKeyword[$iCombine] );
            }
        }
    }

    require( $phpbb_root_path. 'includes/acp/livesearch.'. $phpEx );
    $user-> setup( 'search' );

    $oSearch= new livesearch();

    // Add all language definitions
    foreach( $oSearch-> get_lang_files( TRUE ) as $sLang ) $user-> add_lang( $sLang );

    // What to find: key=raw input, value=regular expression
    $aToFind= array();
    foreach( $aKeyword as $sWord ) $aToFind[$sWord]= preg_quote( $sWord, $oSearch-> sPregBorder );
    $aMatch= $oSearch-> match( $aToFind );

    // Generate the page
    adm_page_header( $user-> lang['SEARCH'] );

    // All matches, if any
    foreach( $aMatch as $sUrl=> $sTitle )
    $template-> assign_block_vars
    ( 'result', array
        ( 'URL'=> $sUrl
        , 'TITLE'=> $sTitle
        )
    );

    $iCount= count( $aMatch );
    $template-> assign_var( 'S_MATCH', sprintf( $user-> lang['FOUND_SEARCH_MATCH'. ( $iCount> 1? 'ES': '' )], $iCount ) );

    $template-> set_custom_template( './style', 'admin' );
    $template-> set_filenames
    ( array
        ( 'body'=> 'acp_livesearch.html'
        )
    );
} else {
/*** 2012-08-23 END AmigoJack ***/  
Find:

Code: Select all

adm_page_footer(); 
Before, add:

Code: Select all

/*** 2012-08-23 BEGIN AmigoJack
    Provide a search to find captions within the ACP. ***/
}
/*** 2012-08-23 END AmigoJack ***/ 
Open /adm/style/overall_header.html and find:

Code: Select all

<div id="wrap">
	<div id="page-header">
After, add:

Code: Select all

		<form action="{U_ADM_INDEX}" method="get" id="search">
		<fieldset style="float: right; margin: 12px 0pt 0pt 10px; padding: 8px">
			<input name="keywords" id="keywords" type="text" maxlength="128" title="{L_SEARCH_KEYWORDS}" class="inputbox search" value="<!-- IF SEARCH_WORDS-->{SEARCH_WORDS}<!-- ELSE -->{L_SEARCH_MINI}<!-- ENDIF -->" onclick="if(this.value=='{LA_SEARCH_MINI}')this.value='';" onblur="if(this.value=='')this.value='{LA_SEARCH_MINI}';" />
			<input class="button2" value="{L_SEARCH}" type="submit" />
			<input name="sid" value="{_SID}" type="hidden" />
			<input name="livesearch" value="livesearch" type="hidden" />
		</fieldset>
		</form>
Create a new file /includes/acp/livesearch.php with the following content:

Code: Select all

<?php
    /*** 2012-08-23 AmigoJack
        Searches language files, PHP files and template files for language identifiers ***/

    if( !defined( 'IN_PHPBB' ) ) exit;


    class livesearch {
        public $sPregBorder= '#';  // RegEx border character

        // Recursively browse a folder to get all filenames
        function browse
        ( &$aList  // Populate with found files: key=path+filename, value=filesize
        , $sPath  // Folder to start at
        , $sExt  // Matching filename extension (PREG term)
        ) {  // No return
            global $phpbb_root_path;

            if( utf8_substr( $sPath, -1 )!= '/' ) $sPath.= '/';  // Ensure trailing backslash

            $hDir= @opendir( $phpbb_root_path. $sPath );
            if( $hDir ) {
                while( ( $sName= readdir( $hDir ) )!== FALSE ) {
                    if( is_file( $phpbb_root_path. $sPath. $sName ) ) {
                        $iSize= filesize( $phpbb_root_path. $sPath. $sName );
                        if( $iSize  // No zero-sized files
                         && preg_match('#\.'. $sExt. '$#i', $sName )  // Only those filenames which match
                          ) {
                            $aList[$phpbb_root_path. $sPath. $sName]= $iSize;  // Add
                        }
                    } else
                    if( $sName[0]!= '.'
                     && is_dir( $phpbb_root_path. $sPath. $sName )  // A folder? Also browse that one
                      ) {
                        $this-> browse( $aList, $sPath. $sName, $sExt );
                    }
                }
                closedir( $hDir );
            }
        }

        // Gets all ACP related language files
        function get_lang_files
        (    $bAsAddLang= FALSE  // Give back array elements ready to be called with session.add_lang()
        ) {
            global $phpEx;

            $aLang= array();

            // Find all language files
            $this-> browse( $aLang, 'language/', $phpEx );

            // Filter out non-ACP
            foreach( $aLang as $sFile=> $iSize )
            if( !preg_match( '#/acp/|/mods/[^/.]+_acp#', $sFile ) ) unset( $aLang[$sFile] );

            if( $bAsAddLang ) {
                $aAddLang= array();

                foreach( $aLang as $sFile=> $iSize )
                $aAddLang[preg_replace
                ( array
                    ( '#^(.*language)?/[a-z]{2}(-[^/]+)?/#'
                    , '#\\.'. $phpEx. '$#'
                    )
                , ''
                , $sFile
                )]= 0;

                $aLang= array_keys( $aAddLang );
            }

            return $aLang;
        }

        function find_lang
        ( $sName  // Language token to find definition for
        ) {
            global $user;

            $sLang= strtoupper( $sName );  // Use class name instead
            if( isset( $user-> lang[$sLang] ) ) return $user-> lang[$sLang]; else
            if( isset( $user-> lang['ACP_'. $sLang] ) ) return $user-> lang['ACP_'. $sLang]; else  // Last try...
            return ucwords( $sName );
        }

        // Performing the search
        function match
        ( $aToFind  // Keywords to search as array: value=PREG term
        ) {  // Returns array of matches: key=url, value=caption
            global $db, $user, $phpEx, $phpbb_admin_path;

            $aLang= $aStyle= $aPhp=
            $aFindLang= $aFindStyle= $aFindPhp= array();

            if( !count( $aToFind ) ) return array();  // Shortcut for no keywords

            $sFind= implode( '|', $aToFind );  // Alternation of all array values

            // Find in language definitions
            $aLang= $this-> get_lang_files();
            foreach( $aLang as $sFile=> $iSize ) {
                $lang= array();
                include( $sFile );

                foreach( $lang as $sKey=> $sLiteral ) {
                    // A value might be an array (which can have arrays aswell), so we simply debugprint it
                    if( is_array( $sLiteral ) ) $sLiteral= print_r( $sLiteral, TRUE );

                    // Found in one of both? Otherwise remove entry, so only found ones survive
                    if( preg_match( $this-> sPregBorder. '('. $sFind. ')'. $this-> sPregBorder. 'isUuS', $sKey )
                     || preg_match( $this-> sPregBorder. '('. $sFind. ')'. $this-> sPregBorder. 'isUuS', $sLiteral )
                        ) continue;
                    unset( $lang[$sKey] );
                }

                // Elements left? Add language keys to the results
                if( count( $lang ) ) {
                    foreach( $lang as $sKey=> $sLiteral )
                    $aFindLang[$sKey]= 0;
                }

                unset( $aLang[$sFile] );
            }


            $sFindStyle= $sFindPhp= '';

            // Prepare finding language keys in ACP style templates
            if( count( $aFindLang ) ) {
                foreach( $aFindLang as $sKey=> $iVoid ) 
                $sFindStyle.= '|'. preg_quote( $sKey, $this-> sPregBorder );
            }
            $sFindStyle.= '|'. $sFind;
            $sFindStyle= utf8_substr( $sFindStyle, 1 );

            $this-> browse( $aStyle, 'adm/style/', 'html' );
            foreach( $aStyle as $sFile=> $iSize ) {
                $sContent= file_get_contents( $sFile );
                if( preg_match( $this-> sPregBorder. '\\{([A-Z]{1,2}_)?('. $sFindStyle. ')\\}'. $this-> sPregBorder. 'u', $sContent ) ) continue;

                unset( $aStyle[$sFile] );  // Not found? Remove
            }


            // Prepare finding one of the found ACP style templates being assigned in a PHP file
            foreach( $aStyle as $sFile=> $iSize ) {
                $sFile= preg_replace( '#^.*/([^/]+)\\.html$#', '$1', $sFile );
                $sFindPhp.= '|'. preg_quote( $sFile, $this-> sPregBorder );
            }
            $sFindPhp= substr( $sFindPhp, 1 );


            // Finally search ACP php files
            if( $sFindStyle  // Language key to search for
             || $sFindPhp  // Template file to search for
              ) $this-> browse( $aPhp, 'includes/acp/', $phpEx );
            foreach( $aPhp as $sFile=> $iSize ) {
                $sContent= file_get_contents( $sFile );
                $bFound= FALSE;
                $aFound= array();

                if( $sFindStyle )
                if( preg_match_all( $this-> sPregBorder. '[\'"]('. $sFindStyle. ')[\'"]'. $this-> sPregBorder. 'u', $sContent, $aMatch, PREG_PATTERN_ORDER| PREG_OFFSET_CAPTURE ) ) {
                    // Found language key directly in PHP file
                    foreach( $aMatch[1] as $iMatch=> $aMatchResult )
                    $aFound[]= array( $aMatchResult[1]=> $aMatchResult[0] );
                }

                if( $sFindPhp )
                if( preg_match_all( $this-> sPregBorder. '->\\s*tpl_name\\s*=\\s*[\'"]('. $sFindPhp. ')[\'"]'. $this-> sPregBorder. 's', $sContent, $aMatch, PREG_PATTERN_ORDER| PREG_OFFSET_CAPTURE ) ) {
                    // Found style file
                    foreach( $aMatch[1] as $iMatch=> $aMatchResult )
                    $aFound[]= array( $aMatchResult[1]=> $aMatchResult[0] );
                }

                // Now find nearest mode
                if( count( $aFound ) ) {
                    if( preg_match_all( '#switch\\s*\\(\\s*\\$([^\\)\\s]*)\\s*\\)|case\\s*[\'"](.*)[\'"]\\s*:#US', $sContent, $aMatch, PREG_PATTERN_ORDER| PREG_OFFSET_CAPTURE ) ) {
                        $bSwitchMode= FALSE;
                        $aMode= array();
                        foreach( $aMatch[0] as $iMatch=> $aMatchResult ) {
                            if( preg_match( '#\\(\\s*\\$[^\\)\\s]*\\s*\\)#', $aMatchResult[0] ) ) {
                                $bSwitchMode= $aMatch[1][$iMatch][0]== 'mode';  // Only search switch blocks which handle 'mode'
                            } else
                            if( preg_match( '#case\\s*[\'"]#', $aMatchResult[0] ) ) {
                                if( $bSwitchMode ) $aMode[$aMatch[2][$iMatch][1]]= $aMatch[2][$iMatch][0];  // Case label
                            }
                        }

                        // Getting nearest mode for each find
                        $aPhp[$sFile]= array();
                        foreach( $aFound as $aFoundLine ) {
                            foreach( $aFoundLine as $iFoundLine=> $sFoundLine ) {
                                $sModeNearest= '';
                                foreach( $aMode as $iLine=> $sMode ) {
                                    if( $iLine<= $iFoundLine ) $sModeNearest= $sMode;
                                }
                                $aPhp[$sFile][]= $sModeNearest;  // This way we're also able to produce links without modes
                            }
                        }
                        $aPhp[$sFile]= array_unique( $aPhp[$sFile] );  // Get rid of dups
                    }
                } else unset( $aPhp[$sFile] );  // Nothing found in this file
            }


            $user-> setup( 'common' );

            // Get all modules to associate captions
            $sql= 'SELECT module_id, module_basename, module_langname, module_mode, module_auth
                FROM '. MODULES_TABLE. '
                WHERE module_class= \'acp\'
                AND module_enabled= 1
                ORDER BY module_basename, module_mode';
            $hResult= $db-> sql_query( $sql );
            $aModule= $aName= array();
            $oModule= new p_master();
            while( $aRow= $db-> sql_fetchrow( $hResult ) ) {
                if( !$oModule-> module_auth( $aRow['module_auth'] ) ) continue;  // Skip inaccessible ones; don't show matches for these

                $aModule[$aRow['module_id']]= $aRow;
                if( $aRow['module_basename'] ) {  // We have no use for categories
                    if( !isset( $aName[$aRow['module_basename']] ) ) $aName[$aRow['module_basename']]= array();
                    $aName[$aRow['module_basename']][$aRow['module_mode']]= $aRow['module_id'];
                }
            }
            $db-> sql_freeresult( $hResult );


            // Find in language definitions - assign module captions
            $aLang= $this-> get_lang_files();
            foreach( $aLang as $sFile=> $iSize ) {
                $lang= array();
                include( $sFile );

                foreach( $aModule as $iModule=> $aModuleEntry )
                if( isset( $lang[$aModuleEntry['module_langname']] ) ) $aModule[$iModule]['lang']= $lang[$aModuleEntry['module_langname']];

                unset( $lang );

                unset( $aLang[$sFile] );
            }


            $aReturn= array();

            foreach( $aPhp as $sFile=> $aMatch ) {
                $sFile= preg_replace( '#^.*/acp_([^/]+)\\.'. $phpEx. '$#', '$1', $sFile );  // Get class name of each filename
                if( !is_array( $aMatch ) ) $aMatch= array( '' );  // If we found a language key but no real occurance in the PHP file itself
                foreach( $aMatch as $sMode ) {
                    if( $sMode  // A mode is set...
                     && !isset( $aName[$sFile][$sMode] )  // ...but it is not known / has no caption
                      ) continue;  // Not authorized to access it

                    $sUrl= append_sid( "{$phpbb_admin_path}index.$phpEx", "i=$sFile". ( $sMode? "&mode=$sMode": '' ) );
                    if( $sMode ) {
                        $sLang= isset( $aModule[$aName[$sFile][$sMode]]['lang'] )? $aModule[$aName[$sFile][$sMode]]['lang']: $aModule[$aName[$sFile][$sMode]]['module_langname']; 
                    }

                    $sTitle= $this-> find_lang( $sFile );
                    if( $sMode ) $sTitle.= ': '. $this-> find_lang( $sMode );

                    $aReturn[$sUrl]= $sTitle;  // Add search match
                }
            }

            return $aReturn;
        }
    } 
Create a new file /adm/style/acp_livesearch.html with the following content:

Code: Select all

<!-- INCLUDE overall_header.html -->

<a name="livesearch"></a>

<!-- IF .result -->
	<h1>{S_MATCH}</h1>
	<ul><!-- BEGIN result -->
		<li><a href="{result.URL}">{result.TITLE}</a></li><!-- END result -->
	</ul>
<!-- ELSE -->
	<h1>{L_NO_SEARCH_RESULTS}</h1>
<!-- ENDIF -->

<!-- INCLUDE overall_footer.html -->
Tested. Adds a keyword search textbox to the ACP. My test search terms were timezone "client communication", which bring up reasonable results. Latest update to this post: 2012-09-04.
  • "The problem is probably not my English but you do not want to understand correctly. ... We will not come anybody anyway, nevertheless, it's best to shit this." Affin, 2018-11-20
  • "But this shit is not here for you. You can follow with your. Maybe the question, instead, was for you, who know, so you shoved us how you are." axe70, 2020-10-10
  • "My reaction is not to everyone, especially to you." Raptiye, 2021-02-28
Renji
Registered User
Posts: 67
Joined: Wed Apr 25, 2012 2:07 pm

Re: Search in ACP

Post by Renji »

Thanks :)
I am not greedy! I just want everything :)
MarkTheDaemon
Former Team Member
Posts: 2771
Joined: Thu Oct 20, 2005 2:42 am
Location: United Kingdom
Name: Mark Barnes

Re: Search in ACP

Post by MarkTheDaemon »

I actually really like this idea, discussion is probably best over at area51 in the Reinventing the ACP topic for 4.0. As has been mentioned in the topic before, this kind of thing really needs to be built from the start.

Return to “[3.0.x] MOD Requests”