1<?php
2/* Copyright (C) 2017	Laurent Destailleur		<eldy@users.sourceforge.net>
3 * Copyright (C) 2017	Regis Houssin			<regis.houssin@inodbox.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19/**
20 *  \file       htdocs/admin/modulehelp.php
21 *  \brief      Page to activate/disable all modules
22 */
23
24if (!defined('NOREQUIREMENU'))  define('NOREQUIREMENU', '1'); // If there is no need to load and show top and left menu
25if (!defined('NOTOKENRENEWAL')) define('NOTOKENRENEWAL', '1'); // Disabled because this page is into a popup on module search page and we want to avoid to have an Anti CSRF token error (done if MAIN_SECURITY_CSRF_WITH_TOKEN is on) when we make a second search after closing popup.
26
27
28require '../main.inc.php';
29require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
30require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
31require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
32
33// Load translation files required by the page
34$langs->loadLangs(array('errors', 'admin', 'modulebuilder', 'exports'));
35
36$mode = GETPOST('mode', 'alpha');
37$action = GETPOST('action', 'aZ09');
38$id = GETPOST('id', 'int');
39if (empty($mode)) $mode = 'desc';
40
41if (!$user->admin)
42	accessforbidden();
43
44
45
46/*
47 * Actions
48 */
49
50// Nothing
51
52
53/*
54 * View
55 */
56
57$form = new Form($db);
58
59$help_url = 'EN:First_setup|FR:Premiers_paramétrages|ES:Primeras_configuraciones';
60llxHeader('', $langs->trans("Setup"), $help_url);
61
62print '<!-- Force style container -->'."\n".'<style>
63.id-container {
64    width: 100%;
65}
66</style>';
67
68$arrayofnatures = array('core'=>$langs->transnoentitiesnoconv("Core"), 'external'=>$langs->transnoentitiesnoconv("External").' - '.$langs->trans("AllPublishers"));
69
70// Search modules dirs
71$modulesdir = dolGetModulesDirs();
72
73
74$filename = array();
75$modules = array();
76$orders = array();
77$categ = array();
78$dirmod = array();
79$i = 0; // is a sequencer of modules found
80$j = 0; // j is module number. Automatically affected if module number not defined.
81$modNameLoaded = array();
82
83foreach ($modulesdir as $dir)
84{
85	// Load modules attributes in arrays (name, numero, orders) from dir directory
86	//print $dir."\n<br>";
87	dol_syslog("Scan directory ".$dir." for module descriptor files (modXXX.class.php)");
88	$handle = @opendir($dir);
89	if (is_resource($handle))
90	{
91		while (($file = readdir($handle)) !== false)
92		{
93			//print "$i ".$file."\n<br>";
94			if (is_readable($dir.$file) && substr($file, 0, 3) == 'mod' && substr($file, dol_strlen($file) - 10) == '.class.php')
95			{
96				$modName = substr($file, 0, dol_strlen($file) - 10);
97
98				if ($modName)
99				{
100					if (!empty($modNameLoaded[$modName]))
101					{
102						$mesg = "Error: Module ".$modName." was found twice: Into ".$modNameLoaded[$modName]." and ".$dir.". You probably have an old file on your disk.<br>";
103						setEventMessages($mesg, null, 'warnings');
104						dol_syslog($mesg, LOG_ERR);
105						continue;
106					}
107
108					try {
109						$res = include_once $dir.$file;
110						if (class_exists($modName))
111						{
112							try {
113								$objMod = new $modName($db);
114								$modNameLoaded[$modName] = $dir;
115
116								if (!$objMod->numero > 0 && $modName != 'modUser')
117								{
118					 				dol_syslog('The module descriptor '.$modName.' must have a numero property', LOG_ERR);
119								}
120								$j = $objMod->numero;
121
122								$modulequalified = 1;
123
124								// We discard modules according to features level (PS: if module is activated we always show it)
125								$const_name = 'MAIN_MODULE_'.strtoupper(preg_replace('/^mod/i', '', get_class($objMod)));
126								if ($objMod->version == 'development' && (empty($conf->global->$const_name) && ($conf->global->MAIN_FEATURES_LEVEL < 2))) $modulequalified = 0;
127								if ($objMod->version == 'experimental' && (empty($conf->global->$const_name) && ($conf->global->MAIN_FEATURES_LEVEL < 1))) $modulequalified = 0;
128								if (preg_match('/deprecated/', $objMod->version) && (empty($conf->global->$const_name) && ($conf->global->MAIN_FEATURES_LEVEL >= 0))) $modulequalified = 0;
129
130								// We discard modules according to property disabled
131								//if (! empty($objMod->hidden)) $modulequalified=0;
132
133								if ($modulequalified > 0)
134								{
135									$publisher = dol_escape_htmltag($objMod->getPublisher());
136									$external = ($objMod->isCoreOrExternalModule() == 'external');
137									if ($external)
138									{
139										if ($publisher)
140										{
141											$arrayofnatures['external_'.$publisher] = $langs->trans("External").' - '.$publisher;
142										} else {
143											$arrayofnatures['external_'] = $langs->trans("External").' - '.$langs->trans("UnknownPublishers");
144										}
145									}
146									ksort($arrayofnatures);
147								}
148
149								// Define array $categ with categ with at least one qualified module
150								if ($modulequalified > 0)
151								{
152									$modules[$i] = $objMod;
153									$filename[$i] = $modName;
154
155									// Gives the possibility to the module, to provide his own family info and position of this family
156									if (is_array($objMod->familyinfo) && !empty($objMod->familyinfo)) {
157										if (!is_array($familyinfo)) $familyinfo = array();
158										$familyinfo = array_merge($familyinfo, $objMod->familyinfo);
159										$familykey = key($objMod->familyinfo);
160									} else {
161										$familykey = $objMod->family;
162									}
163
164									$moduleposition = ($objMod->module_position ? $objMod->module_position : '50');
165									if ($moduleposition == '50' && ($objMod->isCoreOrExternalModule() == 'external'))
166									{
167										$moduleposition = '80'; // External modules at end by default
168									}
169
170									$orders[$i] = $familyinfo[$familykey]['position']."_".$familykey."_".$moduleposition."_".$j; // Sort by family, then by module position then number
171									$dirmod[$i] = $dir;
172									//print $i.'-'.$dirmod[$i].'<br>';
173									// Set categ[$i]
174									$specialstring = 'unknown';
175									if ($objMod->version == 'development' || $objMod->version == 'experimental') $specialstring = 'expdev';
176									if (isset($categ[$specialstring])) $categ[$specialstring]++; // Array of all different modules categories
177									else $categ[$specialstring] = 1;
178									$j++;
179									$i++;
180								} else dol_syslog("Module ".get_class($objMod)." not qualified");
181							} catch (Exception $e)
182							{
183								 dol_syslog("Failed to load ".$dir.$file." ".$e->getMessage(), LOG_ERR);
184							}
185						} else {
186							print "Warning bad descriptor file : ".$dir.$file." (Class ".$modName." not found into file)<br>";
187						}
188					} catch (Exception $e)
189					{
190						 dol_syslog("Failed to load ".$dir.$file." ".$e->getMessage(), LOG_ERR);
191					}
192				}
193			}
194		}
195		closedir($handle);
196	} else {
197		dol_syslog("htdocs/admin/modulehelp.php: Failed to open directory ".$dir.". See permission and open_basedir option.", LOG_WARNING);
198	}
199}
200
201asort($orders);
202//var_dump($orders);
203//var_dump($categ);
204//var_dump($modules);
205
206
207unset($objMod);
208$i = 0;
209foreach ($orders as $tmpkey => $tmpvalue)
210{
211	$tmpMod = $modules[$tmpkey];
212	if ($tmpMod->numero == $id)
213	{
214		$key = $i;
215		$modName = $filename[$tmpkey];
216		$dirofmodule = $dirmod[$tmpkey];
217		$objMod = $tmpMod;
218		break;
219	}
220	$i++;
221}
222$value = $orders[$key];
223$tab = explode('_', $value);
224$familyposition = $tab[0]; $familykey = $tab[1]; $module_position = $tab[2]; $numero = $tab[3];
225
226
227
228$head = modulehelp_prepare_head($objMod);
229
230// Check filters
231$modulename = $objMod->getName();
232$moduledesc = $objMod->getDesc();
233$moduleauthor = $objMod->getPublisher();
234$moduledir = strtolower(preg_replace('/^mod/i', '', get_class($objMod)));
235
236$const_name = 'MAIN_MODULE_'.strtoupper(preg_replace('/^mod/i', '', get_class($objMod)));
237
238$text = '<span class="opacitymedium">'.$langs->trans("LastActivationDate").':</span> ';
239if (!empty($conf->global->$const_name)) $text .= dol_print_date($objMod->getLastActivationDate(), 'dayhour');
240else $text .= $langs->trans("Disabled");
241$tmp = $objMod->getLastActivationInfo();
242$authorid = $tmp['authorid'];
243if ($authorid > 0)
244{
245	$tmpuser = new User($db);
246	$tmpuser->fetch($authorid);
247	$text .= '<br><span class="opacitymedium">'.$langs->trans("LastActivationAuthor").':</span> ';
248	$text .= $tmpuser->getNomUrl(1);
249}
250$ip = $tmp['ip'];
251if ($ip)
252{
253	$text .= '<br><span class="opacitymedium">'.$langs->trans("LastActivationIP").':</span> ';
254	$text .= $ip;
255}
256
257$moreinfo = $text;
258
259$title = ($modulename ? $modulename : $moduledesc);
260
261print '<div class="centpercent">';
262
263$picto = 'object_'.$objMod->picto;
264
265print load_fiche_titre(($modulename ? $modulename : $moduledesc), $moreinfo, $picto, 0, '', 'titlemodulehelp');
266print '<br>';
267
268print dol_get_fiche_head($head, $mode, '', -1);
269
270if (!$modulename)
271{
272	dol_syslog("Error for module ".$key." - Property name of module looks empty", LOG_WARNING);
273}
274
275// Load all lang files of module
276if (isset($objMod->langfiles) && is_array($objMod->langfiles))
277{
278	foreach ($objMod->langfiles as $domain)
279	{
280		$langs->load($domain);
281	}
282}
283
284
285
286
287// Version (with picto warning or not)
288$version = $objMod->getVersion(0);
289$versiontrans = '';
290if (preg_match('/development/i', $version))  $versiontrans .= img_warning($langs->trans("Development"), 'style="float: left"');
291if (preg_match('/experimental/i', $version)) $versiontrans .= img_warning($langs->trans("Experimental"), 'style="float: left"');
292if (preg_match('/deprecated/i', $version))   $versiontrans .= img_warning($langs->trans("Deprecated"), 'style="float: left"');
293$versiontrans .= $objMod->getVersion(1);
294
295// Define imginfo
296$imginfo = "info";
297if ($objMod->isCoreOrExternalModule() == 'external')
298{
299	$imginfo = "info_black";
300}
301
302// Define text of description of module
303$text = '';
304
305if ($mode == 'desc')
306{
307	if ($moduledesc) $text .= '<br>'.$moduledesc.'<br><br><br>';
308
309	$text .= '<span class="opacitymedium">'.$langs->trans("Version").':</span> '.$version;
310
311	$moduledescriptorfile = get_class($objMod).'.class.php';
312	$text .= '<br><span class="opacitymedium">'.$langs->trans("DescriptorFile").':</span> '.$moduledescriptorfile;
313
314	$textexternal = '';
315	if ($objMod->isCoreOrExternalModule() == 'external')
316	{
317		$textexternal .= '<br><span class="opacitymedium">'.$langs->trans("Origin").':</span> '.$langs->trans("ExternalModule").' - '.$langs->trans("InstalledInto", $dirofmodule);
318		if ($objMod->editor_name != 'dolibarr') $textexternal .= '<br><span class="opacitymedium">'.$langs->trans("Publisher").':</span> '.(empty($objMod->editor_name) ? $langs->trans("Unknown") : $objMod->editor_name);
319		$editor_url = $objMod->editor_url;
320		if (!preg_match('/^http/', $editor_url)) $editor_url = 'http://'.$editor_url;
321		if (!empty($objMod->editor_url) && !preg_match('/dolibarr\.org/i', $objMod->editor_url)) $textexternal .= ($objMod->editor_name != 'dolibarr' ? ' - ' : '').img_picto('', 'globe').' <a href="'.$editor_url.'" target="_blank">'.$objMod->editor_url.'</a>';
322		$text .= $textexternal;
323		$text .= '<br>';
324	} else {
325		$text .= '<br><span class="opacitymedium">'.$langs->trans("Origin").':</span> '.$langs->trans("Core").'<br>';
326	}
327
328	$moduledesclong = $objMod->getDescLong();
329	if ($moduledesclong) $text .= '<br><hr><div class="moduledesclong">'.$moduledesclong.'<div>';
330}
331
332if ($mode == 'feature')
333{
334	$text .= '<br><strong>'.$langs->trans("DependsOn").':</strong> ';
335	if (count($objMod->depends)) $text .= join(',', $objMod->depends);
336	else $text .= $langs->trans("None");
337	$text .= '<br><strong>'.$langs->trans("RequiredBy").':</strong> ';
338	if (count($objMod->requiredby)) $text .= join(',', $objMod->requiredby);
339	else $text .= $langs->trans("None");
340
341	$text .= '<br><br>';
342
343	$text .= '<br><strong>'.$langs->trans("AddDataTables").':</strong> ';
344	$sqlfiles = dol_dir_list(dol_buildpath($moduledir.'/sql/'), 'files', 0, 'llx.*\.sql', array('\.key\.sql', '\.sql\.back'));
345	if (count($sqlfiles) > 0)
346	{
347		$text .= $langs->trans("Yes").' (';
348		$i = 0;
349		foreach ($sqlfiles as $val)
350		{
351			$text .= ($i ? ', ' : '').preg_replace('/\.sql$/', '', preg_replace('/llx_/', '', $val['name']));
352			$i++;
353		}
354		$text .= ')';
355	} else $text .= $langs->trans("No");
356
357	$text .= '<br>';
358
359	$text .= '<br><strong>'.$langs->trans("AddDictionaries").':</strong> ';
360	if (isset($objMod->dictionaries) && isset($objMod->dictionaries['tablib']) && is_array($objMod->dictionaries['tablib']) && count($objMod->dictionaries['tablib']))
361	{
362		$i = 0;
363		foreach ($objMod->dictionaries['tablib'] as $val)
364		{
365			$text .= ($i ? ', ' : '').$val;
366			$i++;
367		}
368	} else $text .= $langs->trans("No");
369
370	$text .= '<br>';
371
372	$text .= '<br><strong>'.$langs->trans("AddData").':</strong> ';
373	$filedata = dol_buildpath($moduledir.'/sql/data.sql');
374	if (dol_is_file($filedata))
375	{
376		$text .= $langs->trans("Yes").' ('.$moduledir.'/sql/data.sql)';
377	} else $text .= $langs->trans("No");
378
379	$text .= '<br>';
380
381	$text .= '<br><strong>'.$langs->trans("AddRemoveTabs").':</strong> ';
382	if (isset($objMod->tabs) && is_array($objMod->tabs) && count($objMod->tabs))
383	{
384		$i = 0;
385		foreach ($objMod->tabs as $val)
386		{
387			if (is_array($val)) $val = $val['data'];
388			if (is_string($val))
389			{
390				$tmp = explode(':', $val, 3);
391				$text .= ($i ? ', ' : '').$tmp[0].':'.$tmp[1];
392		   		$i++;
393			}
394		}
395	} else $text .= $langs->trans("No");
396
397	$text .= '<br>';
398
399	$text .= '<br><strong>'.$langs->trans("AddModels").':</strong> ';
400	if (isset($objMod->module_parts) && isset($objMod->module_parts['models']) && $objMod->module_parts['models'])
401	{
402		$text .= $langs->trans("Yes");
403	} else $text .= $langs->trans("No");
404
405	$text .= '<br>';
406
407	$text .= '<br><strong>'.$langs->trans("AddSubstitutions").':</strong> ';
408	if (isset($objMod->module_parts) && isset($objMod->module_parts['substitutions']) && $objMod->module_parts['substitutions'])
409	{
410		$text .= $langs->trans("Yes");
411	} else $text .= $langs->trans("No");
412
413	$text .= '<br>';
414
415	$text .= '<br><strong>'.$langs->trans("AddSheduledJobs").':</strong> ';
416	if (isset($objMod->cronjobs) && is_array($objMod->cronjobs) && count($objMod->cronjobs))
417	{
418		$i = 0;
419		foreach ($objMod->cronjobs as $val)
420		{
421			$text .= ($i ? ', ' : '').($val['label']);
422			$i++;
423		}
424	} else $text .= $langs->trans("No");
425
426	$text .= '<br>';
427
428	$text .= '<br><strong>'.$langs->trans("AddTriggers").':</strong> ';
429	$moreinfoontriggerfile = '';
430	if (isset($objMod->module_parts) && isset($objMod->module_parts['triggers']) && $objMod->module_parts['triggers'])
431	{
432		$yesno = 'Yes';
433	} else {
434		$yesno = 'No';
435	}
436	require_once DOL_DOCUMENT_ROOT.'/core/class/interfaces.class.php';
437	$interfaces = new Interfaces($db);
438	$triggers = $interfaces->getTriggersList(array((($objMod->isCoreOrExternalModule() == 'external') ? '/'.$moduledir : '').'/core/triggers'));
439	foreach ($triggers as $triggercursor)
440	{
441		if ($triggercursor['module'] == $moduledir)
442		{
443			$yesno = 'Yes';
444			$moreinfoontriggerfile = ' ('.$triggercursor['relpath'].')';
445		}
446	}
447
448	$text .= $langs->trans($yesno).$moreinfoontriggerfile;
449
450	$text .= '<br>';
451
452	$text .= '<br><strong>'.$langs->trans("AddBoxes").':</strong> ';
453	if (isset($objMod->boxes) && is_array($objMod->boxes) && count($objMod->boxes))
454	{
455		$i = 0;
456		foreach ($objMod->boxes as $val)
457		{
458			$text .= ($i ? ', ' : '').($val['file'] ? $val['file'] : $val[0]);
459			$i++;
460		}
461	} else $text .= $langs->trans("No");
462
463	$text .= '<br>';
464
465	$text .= '<br><strong>'.$langs->trans("AddHooks").':</strong> ';
466	if (isset($objMod->module_parts) && is_array($objMod->module_parts['hooks']) && count($objMod->module_parts['hooks']))
467	{
468		$i = 0;
469		foreach ($objMod->module_parts['hooks'] as $key => $val)
470		{
471			if ($key === 'entity') continue;
472
473			// For special values
474			if ($key === 'data')
475			{
476				if (is_array($val))
477				{
478					foreach ($val as $value)
479					{
480						$text .= ($i ? ', ' : '').($value);
481						$i++;
482					}
483
484					continue;
485				}
486			}
487
488			$text .= ($i ? ', ' : '').($val);
489			$i++;
490		}
491	} else $text .= $langs->trans("No");
492
493	$text .= '<br>';
494
495	$text .= '<br><strong>'.$langs->trans("AddPermissions").':</strong> ';
496	if (isset($objMod->rights) && is_array($objMod->rights) && count($objMod->rights))
497	{
498		$i = 0;
499		foreach ($objMod->rights as $val)
500		{
501			$text .= ($i ? ', ' : '').($val[1]);
502			$i++;
503		}
504	} else $text .= $langs->trans("No");
505
506	$text .= '<br>';
507
508	$text .= '<br><strong>'.$langs->trans("AddMenus").':</strong> ';
509	if (isset($objMod->menu) && !empty($objMod->menu)) // objMod can be an array or just an int 1
510	{
511		$text .= $langs->trans("Yes");
512	} else $text .= $langs->trans("No");
513
514	$text .= '<br>';
515
516	$text .= '<br><strong>'.$langs->trans("AddExportProfiles").':</strong> ';
517	if (isset($objMod->export_label) && is_array($objMod->export_label) && count($objMod->export_label))
518	{
519		$i = 0;
520		foreach ($objMod->export_label as $val)
521		{
522			$text .= ($i ? ', ' : '').($val);
523			$i++;
524		}
525	} else $text .= $langs->trans("No");
526
527	$text .= '<br>';
528
529	$text .= '<br><strong>'.$langs->trans("AddImportProfiles").':</strong> ';
530	if (isset($objMod->import_label) && is_array($objMod->import_label) && count($objMod->import_label))
531	{
532		$i = 0;
533		foreach ($objMod->import_label as $val)
534		{
535			$text .= ($i ? ', ' : '').($val);
536			$i++;
537		}
538	} else $text .= $langs->trans("No");
539
540	$text .= '<br>';
541
542	$text .= '<br><strong>'.$langs->trans("AddOtherPagesOrServices").':</strong> ';
543	$text .= $langs->trans("DetectionNotPossible");
544}
545
546
547if ($mode == 'changelog')
548{
549	$changelog = $objMod->getChangeLog();
550	if ($changelog) $text .= '<div class="moduledesclong">'.$changelog.'<div>';
551	else $text .= '<div class="moduledesclong">'.$langs->trans("NotAvailable").'</div>';
552}
553
554print $text;
555
556
557print dol_get_fiche_end();
558
559print '</div>';
560
561// End of page
562llxFooter();
563$db->close();
564