[{"data":1,"prerenderedAt":3281},["ShallowReactive",2],{"navigation_docs_en":3,"-development-plugin-development":58,"-development-plugin-development-surround":3276},[4,25,44],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":6},"Getting Started",false,"\u002Fgetting-started","en",[10,15,20],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fgetting-started\u002Fintroduction","en\u002F1.getting-started\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Installation","\u002Fgetting-started\u002Finstallation","en\u002F1.getting-started\u002F2.installation","i-lucide-download",{"title":21,"path":22,"stem":23,"icon":24},"Update & Upgrade","\u002Fgetting-started\u002Fupdate-upgrade","en\u002F1.getting-started\u002F3.update-upgrade","i-lucide-arrow-up-circle",{"title":26,"icon":6,"path":27,"stem":8,"children":28,"page":6},"Guides","\u002Fguides",[29,34,39],{"title":30,"path":31,"stem":32,"icon":33},"Install Styles","\u002Fguides\u002Finstall-styles","en\u002F2.guides\u002F1.install-styles","i-lucide-palette",{"title":35,"path":36,"stem":37,"icon":38},"Install Plugins","\u002Fguides\u002Finstall-plugins","en\u002F2.guides\u002F2.install-plugins","i-lucide-plug",{"title":40,"path":41,"stem":42,"icon":43},"config.php File","\u002Fguides\u002Fconfig-file","en\u002F2.guides\u002F3.config-file","i-lucide-file-cog",{"title":45,"icon":6,"path":46,"stem":8,"children":47,"page":6},"Development","\u002Fdevelopment",[48,53],{"title":49,"path":50,"stem":51,"icon":52},"Plugin Development","\u002Fdevelopment\u002Fplugin-development","en\u002F3.development\u002F1.plugin-development","i-lucide-blocks",{"title":54,"path":55,"stem":56,"icon":57},"Styles Development","\u002Fdevelopment\u002Fstyles-development","en\u002F3.development\u002F2.styles-development","i-lucide-paintbrush",{"id":59,"title":49,"body":60,"description":3267,"extension":3268,"links":3269,"meta":3270,"navigation":3271,"path":50,"seo":3272,"stem":51,"__hash__":3275},"docs_en\u002F3.development\u002F1.plugin-development.md",{"type":61,"value":62,"toc":3229},"minimark",[63,81,84,96,101,125,135,142,146,152,215,227,231,244,319,341,346,352,511,599,614,618,626,651,657,661,680,809,813,817,833,880,890,894,899,950,967,970,992,996,1003,1023,1030,1039,1043,1054,1120,1130,1133,1189,1193,1204,1214,1217,1221,1228,1248,1252,1259,1615,1619,1638,1642,1654,1718,1727,1789,1793,1804,1829,1839,1843,1850,1896,1900,1918,1983,1989,2004,2008,2027,2037,2116,2122,2137,2141,2145,2170,2251,2261,2265,2279,2308,2319,2329,2333,2340,2439,2442,2446,2457,2490,2501,2504,2511,2515,2529,2532,2589,2593,2599,2608,2615,2620,2624,2627,3000,3004,3011,3130,3134,3225],[64,65,66,67,71,72,75,76,80],"p",{},"Kleeja plugins add features to the script without touching core files. A plugin lives in its own folder under ",[68,69,70],"code",{},"plugins\u002F",", declares itself in a single ",[68,73,74],{},"init.php"," file, and attaches callbacks to ",[77,78,79],"em",{},"hooks"," that core code fires while a page runs.",[64,82,83],{},"Because plugins never patch core, upgrading Kleeja does not wipe your changes. The system loads only installed and enabled plugins, so a broken plugin can be disabled from the admin panel instead of edited out by hand.",[85,86,87,88,91,92,95],"note",{},"This page documents the plugin system in Kleeja 3.x. The loader lives in ",[68,89,90],{},"includes\u002Fplugins.php"," and the admin manager in ",[68,93,94],{},"includes\u002Fadm\u002Fj_plugins.php",".",[97,98,100],"h2",{"id":99},"how-it-works","How it works",[64,102,103,104,107,108,111,112,115,116,119,120,122,123,95],{},"On every request, ",[68,105,106],{},"includes\u002Fcommon.php"," boots the ",[68,109,110],{},"Plugins"," singleton. The constructor reads the ",[68,113,114],{},"plugins"," table, keeps the rows where ",[68,117,118],{},"plg_disabled = 0",", and includes the ",[68,121,74],{}," of each matching folder in ",[68,124,70],{},[64,126,127,128,130,131,134],{},"Each ",[68,129,74],{}," fills a ",[68,132,133],{},"$kleeja_plugin"," array with the plugin's metadata, lifecycle callbacks and hook functions. The loader registers those hook functions in a global map, ordered by the plugin's priority.",[64,136,137,138,141],{},"Core files then call ",[68,139,140],{},"Plugins::getInstance()->run('hook_name', get_defined_vars())"," at fixed points. Every function registered for that hook receives the caller's variables, and whatever it returns is merged back into the caller's scope.",[97,143,145],{"id":144},"folder-structure","Folder structure",[64,147,148,149,151],{},"A plugin is a folder whose name matches the key used inside ",[68,150,74],{},". Kleeja reads the folder name from disk and looks it up in the array, so the two must be identical.",[153,154,159],"pre",{"className":155,"code":156,"filename":70,"language":157,"meta":158,"style":158},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","plugins\u002F\n└── my_plugin\u002F\n    ├── init.php      # required — the whole plugin definition\n    ├── icon.png      # optional — shown in the plugin list\n    ├── index.html    # optional — blocks directory listing\n","bash","",[68,160,161,170,180,193,204],{"__ignoreMap":158},[162,163,166],"span",{"class":164,"line":165},"line",1,[162,167,169],{"class":168},"sBMFI","plugins\u002F\n",[162,171,173,176],{"class":164,"line":172},2,[162,174,175],{"class":168},"└──",[162,177,179],{"class":178},"sfazB"," my_plugin\u002F\n",[162,181,183,186,189],{"class":164,"line":182},3,[162,184,185],{"class":168},"    ├──",[162,187,188],{"class":178}," init.php",[162,190,192],{"class":191},"sHwdD","      # required — the whole plugin definition\n",[162,194,196,198,201],{"class":164,"line":195},4,[162,197,185],{"class":168},[162,199,200],{"class":178}," icon.png",[162,202,203],{"class":191},"      # optional — shown in the plugin list\n",[162,205,207,209,212],{"class":164,"line":206},5,[162,208,185],{"class":168},[162,210,211],{"class":178}," index.html",[162,213,214],{"class":191},"    # optional — blocks directory listing\n",[216,217,218,219,222,223,226],"important",{},"The folder name is the plugin key. ",[68,220,221],{},"plugins\u002Fmy_plugin\u002Finit.php"," must define ",[68,224,225],{},"$kleeja_plugin['my_plugin']",", or the loader skips the plugin. Use lowercase letters, digits, dots and underscores, with at least three characters.",[97,228,230],{"id":229},"the-initphp-file","The init.php file",[64,232,233,234,236,237,240,241,243],{},"Every ",[68,235,74],{}," starts with a guard. The ",[68,238,239],{},"IN_PLUGINS_SYSTEM"," constant is defined by ",[68,242,90],{},", so the file exits when someone requests it directly through the browser.",[153,245,249],{"className":246,"code":247,"filename":221,"language":248,"meta":158,"style":158},"language-php shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003C?php\n\u002F\u002F prevent illegal run\nif (! defined('IN_PLUGINS_SYSTEM')) {\n    exit();\n}\n\n$kleeja_plugin['my_plugin']['information'] = [ \u002F* ... *\u002F ];\n$kleeja_plugin['my_plugin']['first_run']['en'] = 'Thanks for installing!';\n$kleeja_plugin['my_plugin']['install']   = function ($plg_id) { \u002F* ... *\u002F };\n$kleeja_plugin['my_plugin']['update']    = function ($old_version, $new_version) { \u002F* ... *\u002F };\n$kleeja_plugin['my_plugin']['uninstall'] = function ($plg_id) { \u002F* ... *\u002F };\n$kleeja_plugin['my_plugin']['functions'] = [ \u002F* hook_name => callable *\u002F ];\n","php",[68,250,251,256,261,266,271,276,283,289,295,301,307,313],{"__ignoreMap":158},[162,252,253],{"class":164,"line":165},[162,254,255],{},"\u003C?php\n",[162,257,258],{"class":164,"line":172},[162,259,260],{},"\u002F\u002F prevent illegal run\n",[162,262,263],{"class":164,"line":182},[162,264,265],{},"if (! defined('IN_PLUGINS_SYSTEM')) {\n",[162,267,268],{"class":164,"line":195},[162,269,270],{},"    exit();\n",[162,272,273],{"class":164,"line":206},[162,274,275],{},"}\n",[162,277,279],{"class":164,"line":278},6,[162,280,282],{"emptyLinePlaceholder":281},true,"\n",[162,284,286],{"class":164,"line":285},7,[162,287,288],{},"$kleeja_plugin['my_plugin']['information'] = [ \u002F* ... *\u002F ];\n",[162,290,292],{"class":164,"line":291},8,[162,293,294],{},"$kleeja_plugin['my_plugin']['first_run']['en'] = 'Thanks for installing!';\n",[162,296,298],{"class":164,"line":297},9,[162,299,300],{},"$kleeja_plugin['my_plugin']['install']   = function ($plg_id) { \u002F* ... *\u002F };\n",[162,302,304],{"class":164,"line":303},10,[162,305,306],{},"$kleeja_plugin['my_plugin']['update']    = function ($old_version, $new_version) { \u002F* ... *\u002F };\n",[162,308,310],{"class":164,"line":309},11,[162,311,312],{},"$kleeja_plugin['my_plugin']['uninstall'] = function ($plg_id) { \u002F* ... *\u002F };\n",[162,314,316],{"class":164,"line":315},12,[162,317,318],{},"$kleeja_plugin['my_plugin']['functions'] = [ \u002F* hook_name => callable *\u002F ];\n",[320,321,322,323,326,327,326,330,333,334,337,338,340],"warning",{},"Define all six keys even when a callback is empty. The loader and the admin manager read ",[68,324,325],{},"information",", ",[68,328,329],{},"update",[68,331,332],{},"install"," and ",[68,335,336],{},"uninstall"," directly, and a missing key raises a PHP warning. A plugin without an ",[68,339,336],{}," callback cannot be uninstalled from the admin panel at all — the manager redirects back to the plugin list.",[342,343,345],"h3",{"id":344},"plugin-information","Plugin information",[64,347,348,349,351],{},"The ",[68,350,325],{}," array describes the plugin to the admin panel and drives compatibility checks and load order.",[353,354,355,371],"table",{},[356,357,358],"thead",{},[359,360,361,365,368],"tr",{},[362,363,364],"th",{},"Key",[362,366,367],{},"Type",[362,369,370],{},"Purpose",[372,373,374,397,415,429,443,457,475,493],"tbody",{},[359,375,376,382,387],{},[377,378,379],"td",{},[68,380,381],{},"plugin_title",[377,383,384],{},[68,385,386],{},"array",[377,388,389,390,393,394,396],{},"Display name per language code, for example ",[68,391,392],{},"['en' => 'My Plugin', 'ar' => 'إضافتي']",". Falls back to ",[68,395,8],{},", then to the first entry.",[359,398,399,404,409],{},[377,400,401],{},[68,402,403],{},"plugin_developer",[377,405,406],{},[68,407,408],{},"string",[377,410,411,412,95],{},"Author name, stored in ",[68,413,414],{},"plg_author",[359,416,417,422,426],{},[377,418,419],{},[68,420,421],{},"plugin_version",[377,423,424],{},[68,425,408],{},[377,427,428],{},"Plugin version. Drives the auto-update check, so bump it on every release.",[359,430,431,436,440],{},[377,432,433],{},[68,434,435],{},"plugin_description",[377,437,438],{},[68,439,386],{},[377,441,442],{},"Short description per language code. Shown in the plugin list, truncated to 100 characters.",[359,444,445,450,454],{},[377,446,447],{},[68,448,449],{},"plugin_kleeja_version_min",[377,451,452],{},[68,453,408],{},[377,455,456],{},"Lowest supported Kleeja version. Installation fails below it.",[359,458,459,464,468],{},[377,460,461],{},[68,462,463],{},"plugin_kleeja_version_max",[377,465,466],{},[68,467,408],{},[377,469,470,471,474],{},"Highest supported Kleeja version. Use ",[68,472,473],{},"0"," for no limit.",[359,476,477,482,487],{},[377,478,479],{},[68,480,481],{},"plugin_priority",[377,483,484],{},[68,485,486],{},"int",[377,488,489,490,492],{},"Load order. Higher numbers run first; ",[68,491,473],{}," is normal.",[359,494,495,500,504],{},[377,496,497],{},[68,498,499],{},"settings_page",[377,501,502],{},[68,503,408],{},[377,505,506,507,510],{},"Optional. Query string of the plugin's settings page, appended to ",[68,508,509],{},"admin\u002F?",". Adds a gear icon next to the plugin.",[153,512,514],{"className":246,"code":513,"filename":221,"language":248,"meta":158,"style":158},"$kleeja_plugin['my_plugin']['information'] = [\n    'plugin_title' => [\n        'en' => 'My Plugin',\n        'ar' => 'إضافتي',\n    ],\n    'plugin_developer' => 'Your Name',\n    'plugin_version'   => '1.0',\n    'plugin_description' => [\n        'en' => 'Does something useful',\n        'ar' => 'تقوم بشيء مفيد',\n    ],\n    'plugin_kleeja_version_min' => '3.2.0',\n    'plugin_kleeja_version_max' => '3.9',\n    'plugin_priority'           => 0,\n    'settings_page'             => 'cp=options&amp;smt=my_plugin',\n];\n",[68,515,516,521,526,531,536,541,546,551,556,561,566,570,575,581,587,593],{"__ignoreMap":158},[162,517,518],{"class":164,"line":165},[162,519,520],{},"$kleeja_plugin['my_plugin']['information'] = [\n",[162,522,523],{"class":164,"line":172},[162,524,525],{},"    'plugin_title' => [\n",[162,527,528],{"class":164,"line":182},[162,529,530],{},"        'en' => 'My Plugin',\n",[162,532,533],{"class":164,"line":195},[162,534,535],{},"        'ar' => 'إضافتي',\n",[162,537,538],{"class":164,"line":206},[162,539,540],{},"    ],\n",[162,542,543],{"class":164,"line":278},[162,544,545],{},"    'plugin_developer' => 'Your Name',\n",[162,547,548],{"class":164,"line":285},[162,549,550],{},"    'plugin_version'   => '1.0',\n",[162,552,553],{"class":164,"line":291},[162,554,555],{},"    'plugin_description' => [\n",[162,557,558],{"class":164,"line":297},[162,559,560],{},"        'en' => 'Does something useful',\n",[162,562,563],{"class":164,"line":303},[162,564,565],{},"        'ar' => 'تقوم بشيء مفيد',\n",[162,567,568],{"class":164,"line":309},[162,569,540],{},[162,571,572],{"class":164,"line":315},[162,573,574],{},"    'plugin_kleeja_version_min' => '3.2.0',\n",[162,576,578],{"class":164,"line":577},13,[162,579,580],{},"    'plugin_kleeja_version_max' => '3.9',\n",[162,582,584],{"class":164,"line":583},14,[162,585,586],{},"    'plugin_priority'           => 0,\n",[162,588,590],{"class":164,"line":589},15,[162,591,592],{},"    'settings_page'             => 'cp=options&amp;smt=my_plugin',\n",[162,594,596],{"class":164,"line":595},16,[162,597,598],{},"];\n",[600,601,602,603,606,607,610,611,613],"tip",{},"Version limits are checked once, at install time, with ",[68,604,605],{},"version_compare()"," against ",[68,608,609],{},"KLEEJA_VERSION",". Set ",[68,612,463],{}," generously — an over-tight maximum blocks installation on newer Kleeja releases even when the plugin still works.",[342,615,617],{"id":616},"first-run-message","First run message",[64,619,348,620,623,624,95],{},[68,621,622],{},"first_run"," key holds an HTML message shown right after a successful install. Kleeja picks the entry matching the site language and falls back to ",[68,625,8],{},[153,627,629],{"className":246,"code":628,"filename":221,"language":248,"meta":158,"style":158},"$kleeja_plugin['my_plugin']['first_run']['en'] = '\nThank you for installing this plugin. Report bugs to: \u003Cbr>\ninfo@example.com\n';\n",[68,630,631,636,641,646],{"__ignoreMap":158},[162,632,633],{"class":164,"line":165},[162,634,635],{},"$kleeja_plugin['my_plugin']['first_run']['en'] = '\n",[162,637,638],{"class":164,"line":172},[162,639,640],{},"Thank you for installing this plugin. Report bugs to: \u003Cbr>\n",[162,642,643],{"class":164,"line":182},[162,644,645],{},"info@example.com\n",[162,647,648],{"class":164,"line":195},[162,649,650],{},"';\n",[64,652,653,654,656],{},"When ",[68,655,622],{}," is missing, the admin panel redirects back to the plugin list after two seconds instead of waiting for a click.",[342,658,660],{"id":659},"install-callback","Install callback",[64,662,348,663,665,666,668,669,672,673,333,676,679],{},[68,664,332],{}," callback runs once, after the plugin row is inserted into the ",[68,667,114],{}," table. It receives the new ",[68,670,671],{},"plg_id",", which you pass to ",[68,674,675],{},"add_config_r()",[68,677,678],{},"add_olang()"," so Kleeja can tie your settings and language strings to the plugin.",[153,681,683],{"className":246,"code":682,"filename":221,"language":248,"meta":158,"style":158},"$kleeja_plugin['my_plugin']['install'] = function ($plg_id) {\n    add_config_r([\n        'my_plugin_enabled' => [\n            'value'  => '1',\n            'html'   => configField('my_plugin_enabled', 'yesno'),\n            'plg_id' => $plg_id,\n            'type'   => 'my_plugin',\n            'order'  => '1',\n        ],\n        'my_plugin_api_key' => [\n            'value'  => '',\n            'html'   => configField('my_plugin_api_key'),\n            'plg_id' => $plg_id,\n            'type'   => 'my_plugin',\n            'order'  => '2',\n        ],\n    ]);\n\n    add_olang([\n        'CONFIG_KLJ_MENUS_MY_PLUGIN' => 'My Plugin settings',\n        'MY_PLUGIN_ENABLED'          => 'Enable the plugin',\n        'MY_PLUGIN_API_KEY'          => 'API key',\n    ], 'en', $plg_id);\n};\n",[68,684,685,690,695,700,705,710,715,720,725,730,735,740,745,749,753,758,762,768,773,779,785,791,797,803],{"__ignoreMap":158},[162,686,687],{"class":164,"line":165},[162,688,689],{},"$kleeja_plugin['my_plugin']['install'] = function ($plg_id) {\n",[162,691,692],{"class":164,"line":172},[162,693,694],{},"    add_config_r([\n",[162,696,697],{"class":164,"line":182},[162,698,699],{},"        'my_plugin_enabled' => [\n",[162,701,702],{"class":164,"line":195},[162,703,704],{},"            'value'  => '1',\n",[162,706,707],{"class":164,"line":206},[162,708,709],{},"            'html'   => configField('my_plugin_enabled', 'yesno'),\n",[162,711,712],{"class":164,"line":278},[162,713,714],{},"            'plg_id' => $plg_id,\n",[162,716,717],{"class":164,"line":285},[162,718,719],{},"            'type'   => 'my_plugin',\n",[162,721,722],{"class":164,"line":291},[162,723,724],{},"            'order'  => '1',\n",[162,726,727],{"class":164,"line":297},[162,728,729],{},"        ],\n",[162,731,732],{"class":164,"line":303},[162,733,734],{},"        'my_plugin_api_key' => [\n",[162,736,737],{"class":164,"line":309},[162,738,739],{},"            'value'  => '',\n",[162,741,742],{"class":164,"line":315},[162,743,744],{},"            'html'   => configField('my_plugin_api_key'),\n",[162,746,747],{"class":164,"line":577},[162,748,714],{},[162,750,751],{"class":164,"line":583},[162,752,719],{},[162,754,755],{"class":164,"line":589},[162,756,757],{},"            'order'  => '2',\n",[162,759,760],{"class":164,"line":595},[162,761,729],{},[162,763,765],{"class":164,"line":764},17,[162,766,767],{},"    ]);\n",[162,769,771],{"class":164,"line":770},18,[162,772,282],{"emptyLinePlaceholder":281},[162,774,776],{"class":164,"line":775},19,[162,777,778],{},"    add_olang([\n",[162,780,782],{"class":164,"line":781},20,[162,783,784],{},"        'CONFIG_KLJ_MENUS_MY_PLUGIN' => 'My Plugin settings',\n",[162,786,788],{"class":164,"line":787},21,[162,789,790],{},"        'MY_PLUGIN_ENABLED'          => 'Enable the plugin',\n",[162,792,794],{"class":164,"line":793},22,[162,795,796],{},"        'MY_PLUGIN_API_KEY'          => 'API key',\n",[162,798,800],{"class":164,"line":799},23,[162,801,802],{},"    ], 'en', $plg_id);\n",[162,804,806],{"class":164,"line":805},24,[162,807,808],{},"};\n",[810,811,812],"caution",{},"Kleeja suppresses SQL errors during installation, so a failing query fails silently. Test the install callback on a scratch database before you publish.",[342,814,816],{"id":815},"update-callback","Update callback",[64,818,819,820,823,824,826,827,829,830,832],{},"The loader compares the installed version stored in ",[68,821,822],{},"plg_ver"," with ",[68,825,421],{}," from ",[68,828,74],{}," on every request. When the file declares a newer version, Kleeja calls ",[68,831,329],{},", then writes the new version to the database.",[153,834,836],{"className":246,"code":835,"filename":221,"language":248,"meta":158,"style":158},"$kleeja_plugin['my_plugin']['update'] = function ($old_version, $new_version) {\n    if (version_compare($old_version, '1.1', '\u003C')) {\n        add_config('my_plugin_timeout', '30', 3, configField('my_plugin_timeout'), 'my_plugin');\n    }\n\n    if (version_compare($old_version, '2.0', '\u003C')) {\n        update_config('my_plugin_api_key', '');\n    }\n};\n",[68,837,838,843,848,853,858,862,867,872,876],{"__ignoreMap":158},[162,839,840],{"class":164,"line":165},[162,841,842],{},"$kleeja_plugin['my_plugin']['update'] = function ($old_version, $new_version) {\n",[162,844,845],{"class":164,"line":172},[162,846,847],{},"    if (version_compare($old_version, '1.1', '\u003C')) {\n",[162,849,850],{"class":164,"line":182},[162,851,852],{},"        add_config('my_plugin_timeout', '30', 3, configField('my_plugin_timeout'), 'my_plugin');\n",[162,854,855],{"class":164,"line":195},[162,856,857],{},"    }\n",[162,859,860],{"class":164,"line":206},[162,861,282],{"emptyLinePlaceholder":281},[162,863,864],{"class":164,"line":278},[162,865,866],{},"    if (version_compare($old_version, '2.0', '\u003C')) {\n",[162,868,869],{"class":164,"line":285},[162,870,871],{},"        update_config('my_plugin_api_key', '');\n",[162,873,874],{"class":164,"line":291},[162,875,857],{},[162,877,878],{"class":164,"line":297},[162,879,808],{},[64,881,882,883,885,886,889],{},"Guard each migration with ",[68,884,605],{}," on ",[68,887,888],{},"$old_version",". Users may upgrade across several releases at once, so the callback must handle every path from any older version to the current one.",[342,891,893],{"id":892},"uninstall-callback","Uninstall callback",[64,895,348,896,898],{},[68,897,336],{}," callback runs before the plugin row is deleted. Remove everything the install callback created — settings, language strings and any custom tables.",[153,900,902],{"className":246,"code":901,"filename":221,"language":248,"meta":158,"style":158},"$kleeja_plugin['my_plugin']['uninstall'] = function ($plg_id) {\n    delete_config([\n        'my_plugin_enabled',\n        'my_plugin_api_key',\n    ]);\n\n    foreach (['ar', 'en'] as $language) {\n        delete_olang(null, $language, $plg_id);\n    }\n};\n",[68,903,904,909,914,919,924,928,932,937,942,946],{"__ignoreMap":158},[162,905,906],{"class":164,"line":165},[162,907,908],{},"$kleeja_plugin['my_plugin']['uninstall'] = function ($plg_id) {\n",[162,910,911],{"class":164,"line":172},[162,912,913],{},"    delete_config([\n",[162,915,916],{"class":164,"line":182},[162,917,918],{},"        'my_plugin_enabled',\n",[162,920,921],{"class":164,"line":195},[162,922,923],{},"        'my_plugin_api_key',\n",[162,925,926],{"class":164,"line":206},[162,927,767],{},[162,929,930],{"class":164,"line":278},[162,931,282],{"emptyLinePlaceholder":281},[162,933,934],{"class":164,"line":285},[162,935,936],{},"    foreach (['ar', 'en'] as $language) {\n",[162,938,939],{"class":164,"line":291},[162,940,941],{},"        delete_olang(null, $language, $plg_id);\n",[162,943,944],{"class":164,"line":297},[162,945,857],{},[162,947,948],{"class":164,"line":303},[162,949,808],{},[85,951,952,953,956,957,960,961,964,965,95],{},"Passing ",[68,954,955],{},"null"," as the first argument to ",[68,958,959],{},"delete_olang()"," with a ",[68,962,963],{},"$plg_id"," deletes every string that belongs to the plugin in that language. This is the cleanest way to undo ",[68,966,678],{},[97,968,969],{"id":79},"Hooks",[64,971,972,973,326,976,326,979,326,982,326,985,988,989,95],{},"Hooks are the plugin system's only entry point into core logic. Kleeja ships more than 200 of them, scattered through ",[68,974,975],{},"index.php",[68,977,978],{},"go.php",[68,980,981],{},"do.php",[68,983,984],{},"ucp.php",[68,986,987],{},"admin\u002Findex.php"," and the files in ",[68,990,991],{},"includes\u002F",[342,993,995],{"id":994},"how-core-fires-a-hook","How core fires a hook",[64,997,998,999,1002],{},"Most core call sites look like this. The hook receives every variable in scope at that point, and ",[68,1000,1001],{},"extract()"," writes the returned values back over them.",[153,1004,1006],{"className":246,"code":1005,"filename":106,"language":248,"meta":158,"style":158},"is_array($plugin_run_result = Plugins::getInstance()->run('end_common', get_defined_vars()))\n    ? extract($plugin_run_result)\n    : null; \u002F\u002Frun hook\n",[68,1007,1008,1013,1018],{"__ignoreMap":158},[162,1009,1010],{"class":164,"line":165},[162,1011,1012],{},"is_array($plugin_run_result = Plugins::getInstance()->run('end_common', get_defined_vars()))\n",[162,1014,1015],{"class":164,"line":172},[162,1016,1017],{},"    ? extract($plugin_run_result)\n",[162,1019,1020],{"class":164,"line":182},[162,1021,1022],{},"    : null; \u002F\u002Frun hook\n",[64,1024,1025,1026,1029],{},"Newer call sites use the ",[68,1027,1028],{},"runHook()"," shorthand, which is equivalent:",[153,1031,1033],{"className":246,"code":1032,"filename":981,"language":248,"meta":158,"style":158},"extract(runHook('begin_download_page', get_defined_vars()));\n",[68,1034,1035],{"__ignoreMap":158},[162,1036,1037],{"class":164,"line":165},[162,1038,1032],{},[342,1040,1042],{"id":1041},"writing-a-hook-function","Writing a hook function",[64,1044,1045,1046,1049,1050,1053],{},"Register hook functions in the ",[68,1047,1048],{},"functions"," array, keyed by hook name. Each function takes a single ",[68,1051,1052],{},"$args"," array and returns an array of variables to write back — or nothing, when it only observes.",[153,1055,1057],{"className":246,"code":1056,"filename":221,"language":248,"meta":158,"style":158},"$kleeja_plugin['my_plugin']['functions'] = [\n    \u002F\u002F change a variable in the caller's scope\n    'Saaheader_links_func' => function ($args) {\n        $extra = $args['extra'] . '\u003Cmeta name=\"generator\" content=\"Kleeja\">';\n\n        return compact('extra');\n    },\n\n    \u002F\u002F observe only — no return value\n    'ok_added_users_register' => function ($args) {\n        my_plugin_log('new user: ' . $args['username']);\n    },\n];\n",[68,1058,1059,1064,1069,1074,1079,1083,1088,1093,1097,1102,1107,1112,1116],{"__ignoreMap":158},[162,1060,1061],{"class":164,"line":165},[162,1062,1063],{},"$kleeja_plugin['my_plugin']['functions'] = [\n",[162,1065,1066],{"class":164,"line":172},[162,1067,1068],{},"    \u002F\u002F change a variable in the caller's scope\n",[162,1070,1071],{"class":164,"line":182},[162,1072,1073],{},"    'Saaheader_links_func' => function ($args) {\n",[162,1075,1076],{"class":164,"line":195},[162,1077,1078],{},"        $extra = $args['extra'] . '\u003Cmeta name=\"generator\" content=\"Kleeja\">';\n",[162,1080,1081],{"class":164,"line":206},[162,1082,282],{"emptyLinePlaceholder":281},[162,1084,1085],{"class":164,"line":278},[162,1086,1087],{},"        return compact('extra');\n",[162,1089,1090],{"class":164,"line":285},[162,1091,1092],{},"    },\n",[162,1094,1095],{"class":164,"line":291},[162,1096,282],{"emptyLinePlaceholder":281},[162,1098,1099],{"class":164,"line":297},[162,1100,1101],{},"    \u002F\u002F observe only — no return value\n",[162,1103,1104],{"class":164,"line":303},[162,1105,1106],{},"    'ok_added_users_register' => function ($args) {\n",[162,1108,1109],{"class":164,"line":309},[162,1110,1111],{},"        my_plugin_log('new user: ' . $args['username']);\n",[162,1113,1114],{"class":164,"line":315},[162,1115,1092],{},[162,1117,1118],{"class":164,"line":577},[162,1119,598],{},[216,1121,1122,1123,1125,1126,1129],{},"Hook functions are closures, so they do not inherit the caller's scope. Read core state from ",[68,1124,1052],{},", and pull globals in explicitly with ",[68,1127,1128],{},"global $config;"," when you need them.",[64,1131,1132],{},"The return value matters:",[353,1134,1135,1145],{},[356,1136,1137],{},[359,1138,1139,1142],{},[362,1140,1141],{},"Return",[362,1143,1144],{},"Effect",[372,1146,1147,1165,1181],{},[359,1148,1149,1158],{},[377,1150,1151,1154,1155],{},[68,1152,1153],{},"compact('var')"," or ",[68,1156,1157],{},"['var' => $value]",[377,1159,1160,1161,1164],{},"Overwrites ",[68,1162,1163],{},"$var"," in the calling scope.",[359,1166,1167,1178],{},[377,1168,1169,326,1172,1174,1175],{},[68,1170,1171],{},"[]",[68,1173,955],{},", or no ",[68,1176,1177],{},"return",[377,1179,1180],{},"Leaves the caller untouched.",[359,1182,1183,1186],{},[377,1184,1185],{},"A new key not present in the caller",[377,1187,1188],{},"Creates that variable in the calling scope.",[342,1190,1192],{"id":1191},"priority-and-chaining","Priority and chaining",[64,1194,1195,1197,1198,1201,1202,95],{},[68,1196,481],{}," controls order. The loader sorts hooks from high priority to low, so a plugin with priority ",[68,1199,1200],{},"10"," runs before one with priority ",[68,1203,473],{},[64,1205,1206,1207,1210,1211,1213],{},"Hooks chain: ",[68,1208,1209],{},"run()"," merges each function's return into ",[68,1212,1052],{}," before calling the next one. A later plugin therefore sees the values an earlier plugin produced, not the original ones.",[320,1215,1216],{},"Give plugins distinct priorities where order matters. The loader keys its internal plugin list by priority, so two plugins sharing a value overwrite each other in that list — their hooks still run, but the bookkeeping is ambiguous.",[342,1218,1220],{"id":1219},"dynamic-hook-names","Dynamic hook names",[64,1222,1223,1224,1227],{},"Some hooks are built from a request variable, which lets a plugin claim a page name that does not exist in core. The admin panel builds three of them from the requested ",[68,1225,1226],{},"cp"," value:",[153,1229,1231],{"className":246,"code":1230,"filename":987,"language":248,"meta":158,"style":158},"Plugins::getInstance()->run(\"require_admin_page_begin_{$go_to}\", get_defined_vars());\nPlugins::getInstance()->run(\"require_admin_page_end_{$go_to}\", get_defined_vars());\nPlugins::getInstance()->run(\"not_exists_{$go_to}\", get_defined_vars());\n",[68,1232,1233,1238,1243],{"__ignoreMap":158},[162,1234,1235],{"class":164,"line":165},[162,1236,1237],{},"Plugins::getInstance()->run(\"require_admin_page_begin_{$go_to}\", get_defined_vars());\n",[162,1239,1240],{"class":164,"line":172},[162,1241,1242],{},"Plugins::getInstance()->run(\"require_admin_page_end_{$go_to}\", get_defined_vars());\n",[162,1244,1245],{"class":164,"line":182},[162,1246,1247],{},"Plugins::getInstance()->run(\"not_exists_{$go_to}\", get_defined_vars());\n",[342,1249,1251],{"id":1250},"hook-reference","Hook reference",[64,1253,1254,1255,1258],{},"A selection of the most useful hooks. Search the codebase with ",[68,1256,1257],{},"grep -rn \"run('\" --include=\"*.php\" ."," for the full list.",[353,1260,1261,1274],{},[356,1262,1263],{},[359,1264,1265,1268,1271],{},[362,1266,1267],{},"Hook",[362,1269,1270],{},"Location",[362,1272,1273],{},"Fires when",[372,1275,1276,1291,1306,1328,1343,1358,1373,1388,1403,1421,1440,1455,1470,1485,1500,1515,1530,1551,1566,1581,1600],{},[359,1277,1278,1283,1288],{},[377,1279,1280],{},[68,1281,1282],{},"boot_common",[377,1284,1285],{},[68,1286,1287],{},"includes\u002Fcommon.php:250",[377,1289,1290],{},"Config, database and template engine are ready.",[359,1292,1293,1298,1303],{},[377,1294,1295],{},[68,1296,1297],{},"end_common",[377,1299,1300],{},[68,1301,1302],{},"includes\u002Fcommon.php:396",[377,1304,1305],{},"Bootstrap has finished, before the page script runs.",[359,1307,1308,1313,1318],{},[377,1309,1310],{},[68,1311,1312],{},"Saaheader_links_func",[377,1314,1315],{},[68,1316,1317],{},"includes\u002Ffunctions_display.php:104",[377,1319,1320,1321,1324,1325,95],{},"Header menus are built — add meta tags via ",[68,1322,1323],{},"$extra",", or menu items via ",[68,1326,1327],{},"$top_menu",[359,1329,1330,1335,1340],{},[377,1331,1332],{},[68,1333,1334],{},"Saaheader_func",[377,1336,1337],{},[68,1338,1339],{},"includes\u002Ffunctions_display.php:150",[377,1341,1342],{},"Header HTML is rendered, before it is echoed.",[359,1344,1345,1350,1355],{},[377,1346,1347],{},[68,1348,1349],{},"Saafooter_func",[377,1351,1352],{},[68,1353,1354],{},"includes\u002Ffunctions_display.php:235",[377,1356,1357],{},"Footer variables are assigned, before rendering.",[359,1359,1360,1365,1370],{},[377,1361,1362],{},[68,1363,1364],{},"print_Saafooter_func",[377,1366,1367],{},[68,1368,1369],{},"includes\u002Ffunctions_display.php:241",[377,1371,1372],{},"Footer HTML is rendered, before it is echoed.",[359,1374,1375,1380,1385],{},[377,1376,1377],{},[68,1378,1379],{},"begin_index_page",[377,1381,1382],{},[68,1383,1384],{},"index.php:30",[377,1386,1387],{},"The upload page starts.",[359,1389,1390,1395,1400],{},[377,1391,1392],{},[68,1393,1394],{},"end_index_page",[377,1396,1397],{},[68,1398,1399],{},"index.php:181",[377,1401,1402],{},"The upload page finishes.",[359,1404,1405,1410,1415],{},[377,1406,1407],{},[68,1408,1409],{},"begin_go_page",[377,1411,1412],{},[68,1413,1414],{},"go.php:21",[377,1416,1417,1418,1420],{},"A ",[68,1419,978],{}," request starts.",[359,1422,1423,1428,1433],{},[377,1424,1425],{},[68,1426,1427],{},"default_go_page",[377,1429,1430],{},[68,1431,1432],{},"go.php:709",[377,1434,1435,1436,1439],{},"The requested ",[68,1437,1438],{},"go"," value matches no core page.",[359,1441,1442,1447,1452],{},[377,1443,1444],{},[68,1445,1446],{},"begin_download_page",[377,1448,1449],{},[68,1450,1451],{},"do.php:17",[377,1453,1454],{},"A download request starts.",[359,1456,1457,1462,1467],{},[377,1458,1459],{},[68,1460,1461],{},"down_go_page",[377,1463,1464],{},[68,1465,1466],{},"do.php:396",[377,1468,1469],{},"The download page is about to be displayed.",[359,1471,1472,1477,1482],{},[377,1473,1474],{},[68,1475,1476],{},"begin_usrcp_page",[377,1478,1479],{},[68,1480,1481],{},"ucp.php:18",[377,1483,1484],{},"A user control panel request starts.",[359,1486,1487,1492,1497],{},[377,1488,1489],{},[68,1490,1491],{},"login_after_submit",[377,1493,1494],{},[68,1495,1496],{},"ucp.php:67",[377,1498,1499],{},"A login form has been submitted.",[359,1501,1502,1507,1512],{},[377,1503,1504],{},[68,1505,1506],{},"ok_added_users_register",[377,1508,1509],{},[68,1510,1511],{},"ucp.php:282",[377,1513,1514],{},"A new user row has been inserted.",[359,1516,1517,1522,1527],{},[377,1518,1519],{},[68,1520,1521],{},"begin_admin_page",[377,1523,1524],{},[68,1525,1526],{},"admin\u002Findex.php:221",[377,1528,1529],{},"The admin panel starts, after extensions are discovered.",[359,1531,1532,1537,1542],{},[377,1533,1534],{},[68,1535,1536],{},"end_admin_page",[377,1538,1539],{},[68,1540,1541],{},"admin\u002Findex.php:404",[377,1543,1544,1545,1154,1548,95],{},"Everything is ready to render — set ",[68,1546,1547],{},"$extra_admin_header_code",[68,1549,1550],{},"$extra_admin_footer_code",[359,1552,1553,1558,1563],{},[377,1554,1555],{},[68,1556,1557],{},"kleeja_send_mail",[377,1559,1560],{},[68,1561,1562],{},"includes\u002Ffunctions.php:242",[377,1564,1565],{},"Kleeja is about to send an email.",[359,1567,1568,1573,1578],{},[377,1569,1570],{},[68,1571,1572],{},"kleeja_fetch_file_start",[377,1574,1575],{},[68,1576,1577],{},"includes\u002FFetchFile.php:79",[377,1579,1580],{},"A remote fetch begins.",[359,1582,1583,1588,1593],{},[377,1584,1585],{},[68,1586,1587],{},"style_parse_func",[377,1589,1590],{},[68,1591,1592],{},"includes\u002Fstyle.php:116",[377,1594,1595,1596,1599],{},"A template is compiled — rewrite ",[68,1597,1598],{},"$html"," to inject markup.",[359,1601,1602,1607,1612],{},[377,1603,1604],{},[68,1605,1606],{},"delete_cache_func",[377,1608,1609],{},[68,1610,1611],{},"includes\u002Ffunctions.php:281",[377,1613,1614],{},"A cache entry is being cleared.",[97,1616,1618],{"id":1617},"adding-settings-to-the-admin-panel","Adding settings to the admin panel",[64,1620,1621,1622,1625,1626,1629,1630,1634,1635,95],{},"Kleeja stores settings in the ",[68,1623,1624],{},"config"," table. A setting with an ",[68,1627,1628],{},"option"," value appears in ",[1631,1632,1633],"strong",{},"Admin → Settings",", grouped into a tab by its ",[68,1636,1637],{},"type",[342,1639,1641],{"id":1640},"registering-settings","Registering settings",[64,1643,1644,1645,1647,1648,1650,1651,1653],{},"Use ",[68,1646,675],{}," in the install callback. Set ",[68,1649,1637],{}," to your plugin's slug so the settings land in their own tab, and pass ",[68,1652,671],{}," so they disappear from the tab list when the plugin is disabled.",[153,1655,1657],{"className":246,"code":1656,"filename":221,"language":248,"meta":158,"style":158},"add_config_r([\n    'my_plugin_mode' => [\n        'value'  => 'fast',\n        'html'   => configField('my_plugin_mode', 'select', [\n            'Fast'     => 'fast',\n            'Accurate' => 'accurate',\n        ]),\n        'plg_id' => $plg_id,\n        'type'   => 'my_plugin',\n        'order'  => '1',\n    ],\n]);\n",[68,1658,1659,1664,1669,1674,1679,1684,1689,1694,1699,1704,1709,1713],{"__ignoreMap":158},[162,1660,1661],{"class":164,"line":165},[162,1662,1663],{},"add_config_r([\n",[162,1665,1666],{"class":164,"line":172},[162,1667,1668],{},"    'my_plugin_mode' => [\n",[162,1670,1671],{"class":164,"line":182},[162,1672,1673],{},"        'value'  => 'fast',\n",[162,1675,1676],{"class":164,"line":195},[162,1677,1678],{},"        'html'   => configField('my_plugin_mode', 'select', [\n",[162,1680,1681],{"class":164,"line":206},[162,1682,1683],{},"            'Fast'     => 'fast',\n",[162,1685,1686],{"class":164,"line":278},[162,1687,1688],{},"            'Accurate' => 'accurate',\n",[162,1690,1691],{"class":164,"line":285},[162,1692,1693],{},"        ]),\n",[162,1695,1696],{"class":164,"line":291},[162,1697,1698],{},"        'plg_id' => $plg_id,\n",[162,1700,1701],{"class":164,"line":297},[162,1702,1703],{},"        'type'   => 'my_plugin',\n",[162,1705,1706],{"class":164,"line":303},[162,1707,1708],{},"        'order'  => '1',\n",[162,1710,1711],{"class":164,"line":309},[162,1712,540],{},[162,1714,1715],{"class":164,"line":315},[162,1716,1717],{},"]);\n",[64,1719,1720,826,1723,1726],{},[68,1721,1722],{},"configField()",[68,1724,1725],{},"includes\u002Ffunctions_display.php"," generates the input HTML in Kleeja's template syntax:",[353,1728,1729,1738],{},[356,1730,1731],{},[359,1732,1733,1735],{},[362,1734,367],{},[362,1736,1737],{},"Renders",[372,1739,1740,1754,1771],{},[359,1741,1742,1747],{},[377,1743,1744],{},[68,1745,1746],{},"text",[377,1748,1749,1750,1753],{},"A text input bound to ",[68,1751,1752],{},"{con.name}",". This is the default.",[359,1755,1756,1761],{},[377,1757,1758],{},[68,1759,1760],{},"yesno",[377,1762,1763,1764,333,1767,1770],{},"A pair of radio buttons using the ",[68,1765,1766],{},"YES",[68,1768,1769],{},"NO"," language strings.",[359,1772,1773,1778],{},[377,1774,1775],{},[68,1776,1777],{},"select",[377,1779,1780,1781,1784,1785,1788],{},"A dropdown built from the ",[68,1782,1783],{},"$select_options"," array, in ",[68,1786,1787],{},"['Label' => 'value']"," form.",[342,1790,1792],{"id":1791},"naming-the-settings-tab","Naming the settings tab",[64,1794,1795,1796,1799,1800,1803],{},"The settings tab reads its label from the language key ",[68,1797,1798],{},"CONFIG_KLJ_MENUS_\u003CTYPE>",", uppercased. Add that string in the install callback, and each setting's label as ",[68,1801,1802],{},"\u003CNAME>"," uppercased.",[153,1805,1807],{"className":246,"code":1806,"filename":221,"language":248,"meta":158,"style":158},"add_olang([\n    'CONFIG_KLJ_MENUS_MY_PLUGIN' => 'My Plugin settings',\n    'MY_PLUGIN_MODE'             => 'Processing mode',\n], 'en', $plg_id);\n",[68,1808,1809,1814,1819,1824],{"__ignoreMap":158},[162,1810,1811],{"class":164,"line":165},[162,1812,1813],{},"add_olang([\n",[162,1815,1816],{"class":164,"line":172},[162,1817,1818],{},"    'CONFIG_KLJ_MENUS_MY_PLUGIN' => 'My Plugin settings',\n",[162,1820,1821],{"class":164,"line":182},[162,1822,1823],{},"    'MY_PLUGIN_MODE'             => 'Processing mode',\n",[162,1825,1826],{"class":164,"line":195},[162,1827,1828],{},"], 'en', $plg_id);\n",[64,1830,1831,1832,1835,1836,95],{},"Without ",[68,1833,1834],{},"CONFIG_KLJ_MENUS_MY_PLUGIN"," the tab still appears, labelled ",[77,1837,1838],{},"Other settings",[342,1840,1842],{"id":1841},"reading-settings-at-runtime","Reading settings at runtime",[64,1844,1845,1846,1849],{},"Settings are cached and exposed through the global ",[68,1847,1848],{},"$config"," array:",[153,1851,1853],{"className":246,"code":1852,"filename":221,"language":248,"meta":158,"style":158},"$kleeja_plugin['my_plugin']['functions'] = [\n    'end_index_page' => function ($args) {\n        global $config;\n\n        if ($config['my_plugin_mode'] === 'fast') {\n            \u002F\u002F ...\n        }\n    },\n];\n",[68,1854,1855,1859,1864,1869,1873,1878,1883,1888,1892],{"__ignoreMap":158},[162,1856,1857],{"class":164,"line":165},[162,1858,1063],{},[162,1860,1861],{"class":164,"line":172},[162,1862,1863],{},"    'end_index_page' => function ($args) {\n",[162,1865,1866],{"class":164,"line":182},[162,1867,1868],{},"        global $config;\n",[162,1870,1871],{"class":164,"line":195},[162,1872,282],{"emptyLinePlaceholder":281},[162,1874,1875],{"class":164,"line":206},[162,1876,1877],{},"        if ($config['my_plugin_mode'] === 'fast') {\n",[162,1879,1880],{"class":164,"line":278},[162,1881,1882],{},"            \u002F\u002F ...\n",[162,1884,1885],{"class":164,"line":285},[162,1886,1887],{},"        }\n",[162,1889,1890],{"class":164,"line":291},[162,1891,1092],{},[162,1893,1894],{"class":164,"line":297},[162,1895,598],{},[97,1897,1899],{"id":1898},"language-strings","Language strings",[64,1901,1902,1903,1906,1907,1910,1911,1914,1915,95],{},"Plugin strings live in the ",[68,1904,1905],{},"lang"," table and are exposed through the global ",[68,1908,1909],{},"$olang"," array, next to core's ",[68,1912,1913],{},"$lang",". Templates read them with ",[68,1916,1917],{},"{olang.KEY}",[353,1919,1920,1932],{},[356,1921,1922],{},[359,1923,1924,1927,1930],{},[362,1925,1926],{},"Function",[362,1928,1929],{},"Signature",[362,1931,370],{},[372,1933,1934,1948,1963],{},[359,1935,1936,1940,1945],{},[377,1937,1938],{},[68,1939,678],{},[377,1941,1942],{},[68,1943,1944],{},"add_olang(array $words, string $lang, int $plg_id)",[377,1946,1947],{},"Insert strings for one language.",[359,1949,1950,1955,1960],{},[377,1951,1952],{},[68,1953,1954],{},"update_olang()",[377,1956,1957],{},[68,1958,1959],{},"update_olang(string $name, string $value, string $lang)",[377,1961,1962],{},"Change one string.",[359,1964,1965,1969,1974],{},[377,1966,1967],{},[68,1968,959],{},[377,1970,1971],{},[68,1972,1973],{},"delete_olang($words, $lang, int $plg_id)",[377,1975,1976,1977,1979,1980,1982],{},"Delete strings; pass ",[68,1978,955],{}," words with a ",[68,1981,671],{}," to remove all of the plugin's.",[64,1984,1985,1986,1988],{},"Call ",[68,1987,678],{}," once per language you ship:",[153,1990,1992],{"className":246,"code":1991,"filename":221,"language":248,"meta":158,"style":158},"add_olang(['MY_PLUGIN_TITLE' => 'My Plugin'], 'en', $plg_id);\nadd_olang(['MY_PLUGIN_TITLE' => 'إضافتي'], 'ar', $plg_id);\n",[68,1993,1994,1999],{"__ignoreMap":158},[162,1995,1996],{"class":164,"line":165},[162,1997,1998],{},"add_olang(['MY_PLUGIN_TITLE' => 'My Plugin'], 'en', $plg_id);\n",[162,2000,2001],{"class":164,"line":172},[162,2002,2003],{},"add_olang(['MY_PLUGIN_TITLE' => 'إضافتي'], 'ar', $plg_id);\n",[97,2005,2007],{"id":2006},"templates-and-styles","Templates and styles",[64,2009,2010,2011,2014,2015,2018,2019,2022,2023,2026],{},"Kleeja renders pages through the ",[68,2012,2013],{},"$tpl"," object. ",[68,2016,2017],{},"display($template_name, $style_path)"," compiles a template into ",[68,2020,2021],{},"cache\u002F"," and returns the HTML, where ",[68,2024,2025],{},"$style_path"," is an absolute directory that overrides the active style.",[64,2028,2029,2030,2033,2034,2036],{},"A plugin can ship its own templates and point Kleeja at them. The bundled ",[68,2031,2032],{},"zaki_admin_theme"," plugin replaces the whole admin theme from the ",[68,2035,1297],{}," hook:",[153,2038,2041],{"className":246,"code":2039,"filename":2040,"language":248,"meta":158,"style":158},"$kleeja_plugin['zaki_admin_theme']['functions'] = [\n    'end_common' => function ($args) {\n        if (! defined('IN_ADMIN')) {\n            return;\n        }\n\n        global $config;\n\n        $args['STYLE_PATH_ADMIN_ABS']   = PATH . 'plugins\u002Fzaki_admin_theme\u002Fzaki\u002F';\n        $args['DEFAULT_PATH_ADMIN_ABS'] = PATH . 'plugins\u002Fzaki_admin_theme\u002Fzaki\u002F';\n        $args['DEFAULT_PATH_ADMIN']     = $config['siteurl'] . 'plugins\u002Fzaki_admin_theme\u002Fzaki\u002F';\n        $args['STYLE_PATH_ADMIN']       = $config['siteurl'] . 'plugins\u002Fzaki_admin_theme\u002Fzaki\u002F';\n\n        return $args;\n    },\n];\n","plugins\u002Fzaki_admin_theme\u002Finit.php",[68,2042,2043,2048,2053,2058,2063,2067,2071,2075,2079,2084,2089,2094,2099,2103,2108,2112],{"__ignoreMap":158},[162,2044,2045],{"class":164,"line":165},[162,2046,2047],{},"$kleeja_plugin['zaki_admin_theme']['functions'] = [\n",[162,2049,2050],{"class":164,"line":172},[162,2051,2052],{},"    'end_common' => function ($args) {\n",[162,2054,2055],{"class":164,"line":182},[162,2056,2057],{},"        if (! defined('IN_ADMIN')) {\n",[162,2059,2060],{"class":164,"line":195},[162,2061,2062],{},"            return;\n",[162,2064,2065],{"class":164,"line":206},[162,2066,1887],{},[162,2068,2069],{"class":164,"line":278},[162,2070,282],{"emptyLinePlaceholder":281},[162,2072,2073],{"class":164,"line":285},[162,2074,1868],{},[162,2076,2077],{"class":164,"line":291},[162,2078,282],{"emptyLinePlaceholder":281},[162,2080,2081],{"class":164,"line":297},[162,2082,2083],{},"        $args['STYLE_PATH_ADMIN_ABS']   = PATH . 'plugins\u002Fzaki_admin_theme\u002Fzaki\u002F';\n",[162,2085,2086],{"class":164,"line":303},[162,2087,2088],{},"        $args['DEFAULT_PATH_ADMIN_ABS'] = PATH . 'plugins\u002Fzaki_admin_theme\u002Fzaki\u002F';\n",[162,2090,2091],{"class":164,"line":309},[162,2092,2093],{},"        $args['DEFAULT_PATH_ADMIN']     = $config['siteurl'] . 'plugins\u002Fzaki_admin_theme\u002Fzaki\u002F';\n",[162,2095,2096],{"class":164,"line":315},[162,2097,2098],{},"        $args['STYLE_PATH_ADMIN']       = $config['siteurl'] . 'plugins\u002Fzaki_admin_theme\u002Fzaki\u002F';\n",[162,2100,2101],{"class":164,"line":577},[162,2102,282],{"emptyLinePlaceholder":281},[162,2104,2105],{"class":164,"line":583},[162,2106,2107],{},"        return $args;\n",[162,2109,2110],{"class":164,"line":589},[162,2111,1092],{},[162,2113,2114],{"class":164,"line":595},[162,2115,598],{},[64,2117,348,2118,2121],{},[68,2119,2120],{},"*_ABS"," variables are filesystem paths used to locate template files; the others are URLs used inside templates to load CSS, JavaScript and images.",[600,2123,2124,2125,2127,2128,2130,2131,2127,2133,2136],{},"To inject markup into an existing template instead of replacing it, hook ",[68,2126,1587],{}," and rewrite ",[68,2129,1598],{}," before compilation, or ",[68,2132,1334],{},[68,2134,2135],{},"$header"," after rendering.",[97,2138,2140],{"id":2139},"adding-new-pages","Adding new pages",[342,2142,2144],{"id":2143},"a-front-end-page","A front-end page",[64,2146,2147,2149,2150,2152,2153,2155,2156,2159,2160,2163,2164,333,2167,95],{},[68,2148,978],{}," fires ",[68,2151,1427],{}," when the ",[68,2154,1438],{}," parameter matches no core page. Set ",[68,2157,2158],{},"$no_request"," to ",[68,2161,2162],{},"false"," to claim the request, then choose a template with ",[68,2165,2166],{},"$stylee",[68,2168,2169],{},"$styleePath",[153,2171,2173],{"className":246,"code":2172,"filename":221,"language":248,"meta":158,"style":158},"$kleeja_plugin['my_plugin']['functions'] = [\n    'default_go_page' => function ($args) {\n        if (g('go', 'str') !== 'mypage') {\n            return;\n        }\n\n        global $tpl;\n\n        $tpl->assign('my_message', 'Hello from my plugin');\n\n        $no_request = false;\n        $stylee     = 'my_page';\n        $styleePath = PATH . 'plugins\u002Fmy_plugin\u002Ftemplates\u002F';\n\n        return compact('no_request', 'stylee', 'styleePath');\n    },\n];\n",[68,2174,2175,2179,2184,2189,2193,2197,2201,2206,2210,2215,2219,2224,2229,2234,2238,2243,2247],{"__ignoreMap":158},[162,2176,2177],{"class":164,"line":165},[162,2178,1063],{},[162,2180,2181],{"class":164,"line":172},[162,2182,2183],{},"    'default_go_page' => function ($args) {\n",[162,2185,2186],{"class":164,"line":182},[162,2187,2188],{},"        if (g('go', 'str') !== 'mypage') {\n",[162,2190,2191],{"class":164,"line":195},[162,2192,2062],{},[162,2194,2195],{"class":164,"line":206},[162,2196,1887],{},[162,2198,2199],{"class":164,"line":278},[162,2200,282],{"emptyLinePlaceholder":281},[162,2202,2203],{"class":164,"line":285},[162,2204,2205],{},"        global $tpl;\n",[162,2207,2208],{"class":164,"line":291},[162,2209,282],{"emptyLinePlaceholder":281},[162,2211,2212],{"class":164,"line":297},[162,2213,2214],{},"        $tpl->assign('my_message', 'Hello from my plugin');\n",[162,2216,2217],{"class":164,"line":303},[162,2218,282],{"emptyLinePlaceholder":281},[162,2220,2221],{"class":164,"line":309},[162,2222,2223],{},"        $no_request = false;\n",[162,2225,2226],{"class":164,"line":315},[162,2227,2228],{},"        $stylee     = 'my_page';\n",[162,2230,2231],{"class":164,"line":577},[162,2232,2233],{},"        $styleePath = PATH . 'plugins\u002Fmy_plugin\u002Ftemplates\u002F';\n",[162,2235,2236],{"class":164,"line":583},[162,2237,282],{"emptyLinePlaceholder":281},[162,2239,2240],{"class":164,"line":589},[162,2241,2242],{},"        return compact('no_request', 'stylee', 'styleePath');\n",[162,2244,2245],{"class":164,"line":595},[162,2246,1092],{},[162,2248,2249],{"class":164,"line":764},[162,2250,598],{},[64,2252,2253,2254,2257,2258,95],{},"Place the template at ",[68,2255,2256],{},"plugins\u002Fmy_plugin\u002Ftemplates\u002Fmy_page.html",". Kleeja then serves the page at ",[68,2259,2260],{},"go.php?go=mypage",[342,2262,2264],{"id":2263},"an-admin-page","An admin page",[64,2266,2267,2268,2271,2272,2275,2276,95],{},"The admin panel builds its menu from the PHP files in ",[68,2269,2270],{},"includes\u002Fadm\u002F",". When the requested page is missing, it fires ",[68,2273,2274],{},"not_exists_{$go_to}"," and includes whatever path you assign to ",[68,2277,2278],{},"$include_alternative",[153,2280,2282],{"className":246,"code":2281,"filename":221,"language":248,"meta":158,"style":158},"'not_exists_mypanel' => function ($args) {\n    $include_alternative = PATH . 'plugins\u002Fmy_plugin\u002Fadmin_page.php';\n\n    return compact('include_alternative');\n},\n",[68,2283,2284,2289,2294,2298,2303],{"__ignoreMap":158},[162,2285,2286],{"class":164,"line":165},[162,2287,2288],{},"'not_exists_mypanel' => function ($args) {\n",[162,2290,2291],{"class":164,"line":172},[162,2292,2293],{},"    $include_alternative = PATH . 'plugins\u002Fmy_plugin\u002Fadmin_page.php';\n",[162,2295,2296],{"class":164,"line":182},[162,2297,282],{"emptyLinePlaceholder":281},[162,2299,2300],{"class":164,"line":195},[162,2301,2302],{},"    return compact('include_alternative');\n",[162,2304,2305],{"class":164,"line":206},[162,2306,2307],{},"},\n",[64,2309,2310,2311,333,2313,2315,2316,95],{},"Your included file sets ",[68,2312,2166],{},[68,2314,2169],{},", exactly like a core admin extension. Reach the page at ",[68,2317,2318],{},"admin\u002F?cp=mypanel",[600,2320,2321,2322,2324,2325,2328],{},"When your plugin only needs a settings screen, skip the custom page and set ",[68,2323,499],{}," in the information array to ",[68,2326,2327],{},"cp=options&amp;smt=my_plugin",". The admin panel then shows a gear icon that links straight to your settings tab.",[97,2330,2332],{"id":2331},"managing-plugins","Managing plugins",[64,2334,2335,2336,2339],{},"Founders manage plugins in ",[1631,2337,2338],{},"Admin → Plugins",". The manager lists three groups: installed plugins, folders present on disk but not yet installed, and plugins available in the remote catalog.",[353,2341,2342,2351],{},[356,2343,2344],{},[359,2345,2346,2349],{},[362,2347,2348],{},"Action",[362,2350,1144],{},[372,2352,2353,2370,2382,2393,2407,2417,2431],{},[359,2354,2355,2358],{},[377,2356,2357],{},"Install",[377,2359,2360,2361,2363,2364,2366,2367,2369],{},"Inserts the ",[68,2362,114],{}," row, runs ",[68,2365,332],{},", shows the ",[68,2368,622],{}," message.",[359,2371,2372,2375],{},[377,2373,2374],{},"Disable",[377,2376,2377,2378,2381],{},"Sets ",[68,2379,2380],{},"plg_disabled = 1",". The plugin stays installed but is not loaded, and its settings tab disappears.",[359,2383,2384,2387],{},[377,2385,2386],{},"Enable",[377,2388,2389,2390,95],{},"Clears ",[68,2391,2392],{},"plg_disabled",[359,2394,2395,2398],{},[377,2396,2397],{},"Uninstall",[377,2399,2400,2401,2403,2404,2406],{},"Runs ",[68,2402,336],{},", deletes the ",[68,2405,114],{}," row. Files stay on disk.",[359,2408,2409,2412],{},[377,2410,2411],{},"Delete folder",[377,2413,2414,2415,95],{},"Removes the plugin folder from ",[68,2416,70],{},[359,2418,2419,2422],{},[377,2420,2421],{},"Upload",[377,2423,2424,2425,2428,2429,95],{},"Extracts an uploaded ",[68,2426,2427],{},".zip"," into ",[68,2430,70],{},[359,2432,2433,2436],{},[377,2434,2435],{},"Download",[377,2437,2438],{},"Fetches and extracts a plugin from the remote catalog, with automatic rollback on failure.",[85,2440,2441],{},"Only founder accounts can install, uninstall, enable, disable or upload plugins. Every action is protected by a CSRF form key.",[97,2443,2445],{"id":2444},"packaging-and-distribution","Packaging and distribution",[64,2447,2448,2449,2451,2452,2454,2455,95],{},"Ship a plugin as a ",[68,2450,2427],{}," archive containing one top-level folder. Administrators upload it in ",[1631,2453,2338],{},", and Kleeja extracts it straight into ",[68,2456,70],{},[153,2458,2461],{"className":155,"code":2459,"filename":2460,"language":157,"meta":158,"style":158},"zip -r my_plugin.zip my_plugin -x \"*.git*\"\n","Terminal",[68,2462,2463],{"__ignoreMap":158},[162,2464,2465,2468,2471,2474,2477,2480,2484,2487],{"class":164,"line":165},[162,2466,2467],{"class":168},"zip",[162,2469,2470],{"class":178}," -r",[162,2472,2473],{"class":178}," my_plugin.zip",[162,2475,2476],{"class":178}," my_plugin",[162,2478,2479],{"class":178}," -x",[162,2481,2483],{"class":2482},"sMK4o"," \"",[162,2485,2486],{"class":178},"*.git*",[162,2488,2489],{"class":2482},"\"\n",[64,2491,2492,2493,2496,2497,2500],{},"Add an ",[68,2494,2495],{},"icon.png"," to the plugin folder for a custom icon in the plugin list, and an empty ",[68,2498,2499],{},"index.html"," to block directory listings on servers that allow them.",[64,2502,2503],{},"Kleeja also reads a public catalog, so listed plugins install with one click:",[2505,2506,2510],"read-more",{"icon":2507,"target":2508,"to":2509},"i-simple-icons-github","_blank","https:\u002F\u002Fgithub.com\u002Fkleeja-official\u002Fstore-catalog","The Kleeja store catalog.",[97,2512,2514],{"id":2513},"debugging","Debugging",[64,2516,2517,2518,2159,2521,2524,2525,2528],{},"Add ",[68,2519,2520],{},"define('DEV_STAGE', true);",[68,2522,2523],{},"config.php"," to unlock the debugging tools. Template caching is disabled, all PHP errors are reported, and a ",[1631,2526,2527],{},"Debug Info"," link appears in the page footer for administrators.",[64,2530,2531],{},"The debug page prints the registered hook map and the list of installed plugins, which is the fastest way to confirm your hook names are spelled correctly:",[153,2533,2535],{"className":246,"code":2534,"filename":90,"language":248,"meta":158,"style":158},"public function getDebugInfo(): array\n{\n    if (!defined('DEV_STAGE')) {\n        return [];\n    }\n\n    return [\n        'all_plugins_hooks' => $this->all_plugins_hooks,\n        'installed_plugins' => $this->installed_plugins,\n    ];\n}\n",[68,2536,2537,2542,2547,2552,2557,2561,2565,2570,2575,2580,2585],{"__ignoreMap":158},[162,2538,2539],{"class":164,"line":165},[162,2540,2541],{},"public function getDebugInfo(): array\n",[162,2543,2544],{"class":164,"line":172},[162,2545,2546],{},"{\n",[162,2548,2549],{"class":164,"line":182},[162,2550,2551],{},"    if (!defined('DEV_STAGE')) {\n",[162,2553,2554],{"class":164,"line":195},[162,2555,2556],{},"        return [];\n",[162,2558,2559],{"class":164,"line":206},[162,2560,857],{},[162,2562,2563],{"class":164,"line":278},[162,2564,282],{"emptyLinePlaceholder":281},[162,2566,2567],{"class":164,"line":285},[162,2568,2569],{},"    return [\n",[162,2571,2572],{"class":164,"line":291},[162,2573,2574],{},"        'all_plugins_hooks' => $this->all_plugins_hooks,\n",[162,2576,2577],{"class":164,"line":297},[162,2578,2579],{},"        'installed_plugins' => $this->installed_plugins,\n",[162,2581,2582],{"class":164,"line":303},[162,2583,2584],{},"    ];\n",[162,2586,2587],{"class":164,"line":309},[162,2588,275],{},[342,2590,2592],{"id":2591},"recovering-from-a-broken-plugin","Recovering from a broken plugin",[64,2594,2595,2596,2598],{},"When a plugin breaks the site badly enough that the admin panel is unreachable, turn the whole system off from ",[68,2597,2523],{},":",[153,2600,2602],{"className":246,"code":2601,"filename":2523,"language":248,"meta":158,"style":158},"define('STOP_PLUGINS', true);\n",[68,2603,2604],{"__ignoreMap":158},[162,2605,2606],{"class":164,"line":165},[162,2607,2601],{},[64,2609,2610,2611,2614],{},"The loader returns immediately, no plugin is included, and the footer reports ",[68,2612,2613],{},"Hook System: Disabled",". Remove the line once you have disabled or deleted the offending plugin.",[810,2616,2617,2619],{},[68,2618,74],{}," is included on every single request. Keep it to declarations — never run queries, network calls or file scans at the top level, or you slow down every page on the site.",[97,2621,2623],{"id":2622},"complete-example","Complete example",[64,2625,2626],{},"A minimal plugin that adds a setting and appends a custom footer note.",[153,2628,2631],{"className":246,"code":2629,"filename":2630,"language":248,"meta":158,"style":158},"\u003C?php\n\u002F\u002F prevent illegal run\nif (! defined('IN_PLUGINS_SYSTEM')) {\n    exit();\n}\n\n$kleeja_plugin['footer_note']['information'] = [\n    'plugin_title' => [\n        'en' => 'Footer Note',\n        'ar' => 'ملاحظة التذييل',\n    ],\n    'plugin_developer' => 'Your Name',\n    'plugin_version'   => '1.0',\n    'plugin_description' => [\n        'en' => 'Appends a custom note to the site footer',\n        'ar' => 'تضيف ملاحظة مخصصة إلى تذييل الموقع',\n    ],\n    'plugin_kleeja_version_min' => '3.2.0',\n    'plugin_kleeja_version_max' => '3.9',\n    'plugin_priority'           => 0,\n    'settings_page'             => 'cp=options&amp;smt=footer_note',\n];\n\n$kleeja_plugin['footer_note']['first_run']['en'] = 'Set your note in Admin → Settings → Footer note.';\n\n$kleeja_plugin['footer_note']['install'] = function ($plg_id) {\n    add_config_r([\n        'footer_note_text' => [\n            'value'  => '',\n            'html'   => configField('footer_note_text'),\n            'plg_id' => $plg_id,\n            'type'   => 'footer_note',\n            'order'  => '1',\n        ],\n    ]);\n\n    add_olang([\n        'CONFIG_KLJ_MENUS_FOOTER_NOTE' => 'Footer note',\n        'FOOTER_NOTE_TEXT'             => 'Note text',\n    ], 'en', $plg_id);\n\n    add_olang([\n        'CONFIG_KLJ_MENUS_FOOTER_NOTE' => 'ملاحظة التذييل',\n        'FOOTER_NOTE_TEXT'             => 'نص الملاحظة',\n    ], 'ar', $plg_id);\n};\n\n$kleeja_plugin['footer_note']['update'] = function ($old_version, $new_version) {\n    \u002F\u002F no migrations yet\n};\n\n$kleeja_plugin['footer_note']['uninstall'] = function ($plg_id) {\n    delete_config(['footer_note_text']);\n\n    foreach (['ar', 'en'] as $language) {\n        delete_olang(null, $language, $plg_id);\n    }\n};\n\n$kleeja_plugin['footer_note']['functions'] = [\n    'print_Saafooter_func' => function ($args) {\n        global $config;\n\n        if (empty($config['footer_note_text'])) {\n            return;\n        }\n\n        $note   = '\u003Cp class=\"footer-note\">' . htmlspecialchars($config['footer_note_text']) . '\u003C\u002Fp>';\n        $footer = str_replace('\u003C\u002Fbody>', $note . '\u003C\u002Fbody>', $args['footer']);\n\n        return compact('footer');\n    },\n];\n","plugins\u002Ffooter_note\u002Finit.php",[68,2632,2633,2637,2641,2645,2649,2653,2657,2662,2666,2671,2676,2680,2684,2688,2692,2697,2702,2706,2710,2714,2718,2723,2727,2731,2736,2741,2747,2752,2758,2763,2769,2774,2780,2785,2790,2795,2800,2805,2811,2817,2822,2827,2832,2838,2844,2850,2855,2860,2866,2872,2877,2882,2888,2894,2899,2904,2909,2914,2919,2924,2930,2936,2941,2946,2952,2957,2962,2967,2973,2979,2984,2990,2995],{"__ignoreMap":158},[162,2634,2635],{"class":164,"line":165},[162,2636,255],{},[162,2638,2639],{"class":164,"line":172},[162,2640,260],{},[162,2642,2643],{"class":164,"line":182},[162,2644,265],{},[162,2646,2647],{"class":164,"line":195},[162,2648,270],{},[162,2650,2651],{"class":164,"line":206},[162,2652,275],{},[162,2654,2655],{"class":164,"line":278},[162,2656,282],{"emptyLinePlaceholder":281},[162,2658,2659],{"class":164,"line":285},[162,2660,2661],{},"$kleeja_plugin['footer_note']['information'] = [\n",[162,2663,2664],{"class":164,"line":291},[162,2665,525],{},[162,2667,2668],{"class":164,"line":297},[162,2669,2670],{},"        'en' => 'Footer Note',\n",[162,2672,2673],{"class":164,"line":303},[162,2674,2675],{},"        'ar' => 'ملاحظة التذييل',\n",[162,2677,2678],{"class":164,"line":309},[162,2679,540],{},[162,2681,2682],{"class":164,"line":315},[162,2683,545],{},[162,2685,2686],{"class":164,"line":577},[162,2687,550],{},[162,2689,2690],{"class":164,"line":583},[162,2691,555],{},[162,2693,2694],{"class":164,"line":589},[162,2695,2696],{},"        'en' => 'Appends a custom note to the site footer',\n",[162,2698,2699],{"class":164,"line":595},[162,2700,2701],{},"        'ar' => 'تضيف ملاحظة مخصصة إلى تذييل الموقع',\n",[162,2703,2704],{"class":164,"line":764},[162,2705,540],{},[162,2707,2708],{"class":164,"line":770},[162,2709,574],{},[162,2711,2712],{"class":164,"line":775},[162,2713,580],{},[162,2715,2716],{"class":164,"line":781},[162,2717,586],{},[162,2719,2720],{"class":164,"line":787},[162,2721,2722],{},"    'settings_page'             => 'cp=options&amp;smt=footer_note',\n",[162,2724,2725],{"class":164,"line":793},[162,2726,598],{},[162,2728,2729],{"class":164,"line":799},[162,2730,282],{"emptyLinePlaceholder":281},[162,2732,2733],{"class":164,"line":805},[162,2734,2735],{},"$kleeja_plugin['footer_note']['first_run']['en'] = 'Set your note in Admin → Settings → Footer note.';\n",[162,2737,2739],{"class":164,"line":2738},25,[162,2740,282],{"emptyLinePlaceholder":281},[162,2742,2744],{"class":164,"line":2743},26,[162,2745,2746],{},"$kleeja_plugin['footer_note']['install'] = function ($plg_id) {\n",[162,2748,2750],{"class":164,"line":2749},27,[162,2751,694],{},[162,2753,2755],{"class":164,"line":2754},28,[162,2756,2757],{},"        'footer_note_text' => [\n",[162,2759,2761],{"class":164,"line":2760},29,[162,2762,739],{},[162,2764,2766],{"class":164,"line":2765},30,[162,2767,2768],{},"            'html'   => configField('footer_note_text'),\n",[162,2770,2772],{"class":164,"line":2771},31,[162,2773,714],{},[162,2775,2777],{"class":164,"line":2776},32,[162,2778,2779],{},"            'type'   => 'footer_note',\n",[162,2781,2783],{"class":164,"line":2782},33,[162,2784,724],{},[162,2786,2788],{"class":164,"line":2787},34,[162,2789,729],{},[162,2791,2793],{"class":164,"line":2792},35,[162,2794,767],{},[162,2796,2798],{"class":164,"line":2797},36,[162,2799,282],{"emptyLinePlaceholder":281},[162,2801,2803],{"class":164,"line":2802},37,[162,2804,778],{},[162,2806,2808],{"class":164,"line":2807},38,[162,2809,2810],{},"        'CONFIG_KLJ_MENUS_FOOTER_NOTE' => 'Footer note',\n",[162,2812,2814],{"class":164,"line":2813},39,[162,2815,2816],{},"        'FOOTER_NOTE_TEXT'             => 'Note text',\n",[162,2818,2820],{"class":164,"line":2819},40,[162,2821,802],{},[162,2823,2825],{"class":164,"line":2824},41,[162,2826,282],{"emptyLinePlaceholder":281},[162,2828,2830],{"class":164,"line":2829},42,[162,2831,778],{},[162,2833,2835],{"class":164,"line":2834},43,[162,2836,2837],{},"        'CONFIG_KLJ_MENUS_FOOTER_NOTE' => 'ملاحظة التذييل',\n",[162,2839,2841],{"class":164,"line":2840},44,[162,2842,2843],{},"        'FOOTER_NOTE_TEXT'             => 'نص الملاحظة',\n",[162,2845,2847],{"class":164,"line":2846},45,[162,2848,2849],{},"    ], 'ar', $plg_id);\n",[162,2851,2853],{"class":164,"line":2852},46,[162,2854,808],{},[162,2856,2858],{"class":164,"line":2857},47,[162,2859,282],{"emptyLinePlaceholder":281},[162,2861,2863],{"class":164,"line":2862},48,[162,2864,2865],{},"$kleeja_plugin['footer_note']['update'] = function ($old_version, $new_version) {\n",[162,2867,2869],{"class":164,"line":2868},49,[162,2870,2871],{},"    \u002F\u002F no migrations yet\n",[162,2873,2875],{"class":164,"line":2874},50,[162,2876,808],{},[162,2878,2880],{"class":164,"line":2879},51,[162,2881,282],{"emptyLinePlaceholder":281},[162,2883,2885],{"class":164,"line":2884},52,[162,2886,2887],{},"$kleeja_plugin['footer_note']['uninstall'] = function ($plg_id) {\n",[162,2889,2891],{"class":164,"line":2890},53,[162,2892,2893],{},"    delete_config(['footer_note_text']);\n",[162,2895,2897],{"class":164,"line":2896},54,[162,2898,282],{"emptyLinePlaceholder":281},[162,2900,2902],{"class":164,"line":2901},55,[162,2903,936],{},[162,2905,2907],{"class":164,"line":2906},56,[162,2908,941],{},[162,2910,2912],{"class":164,"line":2911},57,[162,2913,857],{},[162,2915,2917],{"class":164,"line":2916},58,[162,2918,808],{},[162,2920,2922],{"class":164,"line":2921},59,[162,2923,282],{"emptyLinePlaceholder":281},[162,2925,2927],{"class":164,"line":2926},60,[162,2928,2929],{},"$kleeja_plugin['footer_note']['functions'] = [\n",[162,2931,2933],{"class":164,"line":2932},61,[162,2934,2935],{},"    'print_Saafooter_func' => function ($args) {\n",[162,2937,2939],{"class":164,"line":2938},62,[162,2940,1868],{},[162,2942,2944],{"class":164,"line":2943},63,[162,2945,282],{"emptyLinePlaceholder":281},[162,2947,2949],{"class":164,"line":2948},64,[162,2950,2951],{},"        if (empty($config['footer_note_text'])) {\n",[162,2953,2955],{"class":164,"line":2954},65,[162,2956,2062],{},[162,2958,2960],{"class":164,"line":2959},66,[162,2961,1887],{},[162,2963,2965],{"class":164,"line":2964},67,[162,2966,282],{"emptyLinePlaceholder":281},[162,2968,2970],{"class":164,"line":2969},68,[162,2971,2972],{},"        $note   = '\u003Cp class=\"footer-note\">' . htmlspecialchars($config['footer_note_text']) . '\u003C\u002Fp>';\n",[162,2974,2976],{"class":164,"line":2975},69,[162,2977,2978],{},"        $footer = str_replace('\u003C\u002Fbody>', $note . '\u003C\u002Fbody>', $args['footer']);\n",[162,2980,2982],{"class":164,"line":2981},70,[162,2983,282],{"emptyLinePlaceholder":281},[162,2985,2987],{"class":164,"line":2986},71,[162,2988,2989],{},"        return compact('footer');\n",[162,2991,2993],{"class":164,"line":2992},72,[162,2994,1092],{},[162,2996,2998],{"class":164,"line":2997},73,[162,2999,598],{},[97,3001,3003],{"id":3002},"api-reference","API reference",[64,3005,3006,3007,3010],{},"Helper functions available to plugins, all defined in ",[68,3008,3009],{},"includes\u002Ffunctions.php"," unless noted.",[353,3012,3013,3021],{},[356,3014,3015],{},[359,3016,3017,3019],{},[362,3018,1926],{},[362,3020,1929],{},[372,3022,3023,3035,3046,3058,3070,3081,3092,3103,3117],{},[359,3024,3025,3030],{},[377,3026,3027],{},[68,3028,3029],{},"add_config()",[377,3031,3032],{},[68,3033,3034],{},"add_config(string $name, string $value, int $order = 0, string $html = '', string $type = '0', int $plg_id = 0, bool $dynamic = false): bool",[359,3036,3037,3041],{},[377,3038,3039],{},[68,3040,675],{},[377,3042,3043],{},[68,3044,3045],{},"add_config_r(array $configs): bool",[359,3047,3048,3053],{},[377,3049,3050],{},[68,3051,3052],{},"update_config()",[377,3054,3055],{},[68,3056,3057],{},"update_config(string $name, string $value, bool $escape = true, int $group = 0): bool",[359,3059,3060,3065],{},[377,3061,3062],{},[68,3063,3064],{},"delete_config()",[377,3066,3067],{},[68,3068,3069],{},"delete_config(string|array $name): bool",[359,3071,3072,3076],{},[377,3073,3074],{},[68,3075,678],{},[377,3077,3078],{},[68,3079,3080],{},"add_olang(array $words = [], string $lang = 'en', int $plg_id = 0): void",[359,3082,3083,3087],{},[377,3084,3085],{},[68,3086,1954],{},[377,3088,3089],{},[68,3090,3091],{},"update_olang(string $name, string $value, string $lang = 'en'): bool",[359,3093,3094,3098],{},[377,3095,3096],{},[68,3097,959],{},[377,3099,3100],{},[68,3101,3102],{},"delete_olang(string|array $words = '', string $lang = 'en', int $plg_id = 0): bool",[359,3104,3105,3109],{},[377,3106,3107],{},[68,3108,1722],{},[377,3110,3111,3114,3115],{},[68,3112,3113],{},"configField(string $name, string $type = 'text', array $select_options = []): string"," — ",[68,3116,1725],{},[359,3118,3119,3123],{},[377,3120,3121],{},[68,3122,1028],{},[377,3124,3125,3114,3128],{},[68,3126,3127],{},"runHook(string $hookName, array $definedVariables): array",[68,3129,90],{},[342,3131,3133],{"id":3132},"constants","Constants",[353,3135,3136,3146],{},[356,3137,3138],{},[359,3139,3140,3143],{},[362,3141,3142],{},"Constant",[362,3144,3145],{},"Meaning",[372,3147,3148,3160,3173,3186,3195,3205,3215],{},[359,3149,3150,3154],{},[377,3151,3152],{},[68,3153,239],{},[377,3155,3156,3157,3159],{},"Defined while plugins load. Guard every ",[68,3158,74],{}," with it.",[359,3161,3162,3167],{},[377,3163,3164],{},[68,3165,3166],{},"STOP_PLUGINS",[377,3168,3169,3170,3172],{},"Defined in ",[68,3171,2523],{}," to disable the whole plugin system.",[359,3174,3175,3180],{},[377,3176,3177],{},[68,3178,3179],{},"KLEEJA_PLUGINS_FOLDER",[377,3181,3182,3183,3185],{},"The plugins directory name, ",[68,3184,114],{}," by default.",[359,3187,3188,3192],{},[377,3189,3190],{},[68,3191,609],{},[377,3193,3194],{},"The running Kleeja version, used for compatibility checks.",[359,3196,3197,3202],{},[377,3198,3199],{},[68,3200,3201],{},"PATH",[377,3203,3204],{},"Absolute filesystem path to the Kleeja root, with a trailing slash.",[359,3206,3207,3212],{},[377,3208,3209],{},[68,3210,3211],{},"DEV_STAGE",[377,3213,3214],{},"Enables debugging output and disables template caching.",[359,3216,3217,3222],{},[377,3218,3219],{},[68,3220,3221],{},"IN_ADMIN",[377,3223,3224],{},"Defined while the admin panel renders.",[3226,3227,3228],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}",{"title":158,"searchDepth":172,"depth":172,"links":3230},[3231,3232,3233,3240,3247,3252,3253,3254,3258,3259,3260,3263,3264],{"id":99,"depth":172,"text":100},{"id":144,"depth":172,"text":145},{"id":229,"depth":172,"text":230,"children":3234},[3235,3236,3237,3238,3239],{"id":344,"depth":182,"text":345},{"id":616,"depth":182,"text":617},{"id":659,"depth":182,"text":660},{"id":815,"depth":182,"text":816},{"id":892,"depth":182,"text":893},{"id":79,"depth":172,"text":969,"children":3241},[3242,3243,3244,3245,3246],{"id":994,"depth":182,"text":995},{"id":1041,"depth":182,"text":1042},{"id":1191,"depth":182,"text":1192},{"id":1219,"depth":182,"text":1220},{"id":1250,"depth":182,"text":1251},{"id":1617,"depth":172,"text":1618,"children":3248},[3249,3250,3251],{"id":1640,"depth":182,"text":1641},{"id":1791,"depth":182,"text":1792},{"id":1841,"depth":182,"text":1842},{"id":1898,"depth":172,"text":1899},{"id":2006,"depth":172,"text":2007},{"id":2139,"depth":172,"text":2140,"children":3255},[3256,3257],{"id":2143,"depth":182,"text":2144},{"id":2263,"depth":182,"text":2264},{"id":2331,"depth":172,"text":2332},{"id":2444,"depth":172,"text":2445},{"id":2513,"depth":172,"text":2514,"children":3261},[3262],{"id":2591,"depth":182,"text":2592},{"id":2622,"depth":172,"text":2623},{"id":3002,"depth":172,"text":3003,"children":3265},[3266],{"id":3132,"depth":182,"text":3133},"Learn how to build your own plugins for Kleeja.","md",null,{},{"icon":52},{"title":3273,"description":3274},"Kleeja plugin development","A guide to building your own plugins for Kleeja.","g89DI0XudM5U4GXauUZSD4k_MKCcvE9mX34fZxkNYpQ",[3277,3279],{"title":40,"path":41,"stem":42,"description":3278,"icon":43,"children":-1},"Kleeja's main configuration file — where it lives, how to create it by hand, and every advanced option you can add to it.",{"title":54,"path":55,"stem":56,"description":3280,"icon":57,"children":-1},"Learn how to build your own styles for Kleeja.",1788049177132]