/*******************************************************************************
* Copyright (c) 2009, 2014 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Zend Technologies
*******************************************************************************/
package org.eclipse.php.internal.debug.core;
import java.io.*;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.preferences.DefaultScope;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.IScopeContext;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.debug.core.model.IDebugElement;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.internal.ui.DebugUIPlugin;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.php.core.PHPVersion;
import org.eclipse.php.core.project.ProjectOptions;
import org.eclipse.php.internal.debug.core.debugger.DebuggerSettingsManager;
import org.eclipse.php.internal.debug.core.debugger.IDebuggerConfiguration;
import org.eclipse.php.internal.debug.core.launching.PHPProcess;
import org.eclipse.php.internal.debug.core.launching.XDebugLaunchListener;
import org.eclipse.php.internal.debug.core.preferences.*;
import org.eclipse.php.internal.debug.core.xdebug.XDebugPreferenceMgr;
import org.eclipse.php.internal.debug.core.xdebug.dbgp.DBGpProxyHandlersManager;
import org.eclipse.php.internal.debug.core.zend.communication.DebuggerCommunicationDaemon;
import org.eclipse.php.internal.debug.core.zend.debugger.IRemoteDebugger;
import org.eclipse.php.internal.debug.core.zend.model.PHPDebugTarget;
import org.eclipse.php.internal.debug.daemon.DaemonPlugin;
import org.eclipse.php.internal.server.core.Server;
import org.eclipse.php.internal.server.core.manager.ServersManager;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
/**
* The PHP Debug plug-in class.
*/
public class PHPDebugPlugin extends Plugin {
/**
* This class is used to start separate non-UI job just right after
* encompassing bundle is started to perform "right-after startup"
* operations. Everything that doesn't need UI thread and is not required to
* be initialized only while starting bundle should be placed here.
*/
private class PostStart implements BundleListener {
@Override
public void bundleChanged(BundleEvent event) {
if (event.getBundle() == getBundle() && event.getType() == BundleEvent.STARTED) {
Job handler = new Job(PHPDebugCoreMessages.PHPDebugPlugin_PostStartup) {
@Override
protected IStatus run(IProgressMonitor monitor) {
// Perform 'Post Startup'
try {
monitor.beginTask(PHPDebugCoreMessages.PHPDebugPlugin_PerformingPostStartupOperations,
IProgressMonitor.UNKNOWN);
perform();
} catch (Exception e) {
Logger.logException(MessageFormat.format(
"Errors occurred while performing ''{0}'' bundle post startup.", //$NON-NLS-1$
ID), e);
} finally {
// Unregister itself from listeners
getBundle().getBundleContext().removeBundleListener(PostStart.this);
monitor.done();
}
return Status.OK_STATUS;
}
};
// Hide from the user
handler.setUser(false);
handler.setSystem(true);
handler.schedule();
}
}
private void perform() {
// Set the auto-remove old launches listener
IPreferenceStore preferenceStore = DebugUIPlugin.getDefault().getPreferenceStore();
fInitialAutoRemoveLaunches = preferenceStore.getBoolean(IDebugUIConstants.PREF_AUTO_REMOVE_OLD_LAUNCHES);
preferenceStore.addPropertyChangeListener(new AutoRemoveOldLaunchesListener());
org.eclipse.php.internal.server.core.Activator.getDefault();
XDebugPreferenceMgr.setDefaults();
XDebugLaunchListener.getInstance();
DaemonPlugin.getDefault();
DebuggerSettingsManager.INSTANCE.startup();
DBGpProxyHandlersManager.INSTANCE.startup();
}
}
private static class ExtDirSupport {
private static final String EXTENSION_POINT_NAME = "phpExe"; //$NON-NLS-1$
private static final String LOCATION_ATTRIBUTE = "location"; //$NON-NLS-1$
private static final String PHPEXE_TAG = "phpExe"; //$NON-NLS-1$
/**
* Sets extension_dir in php.ini file for all php executables (i.e.
* resources/php53, resources/php54) provided by extension point
* "phpExe", for OSs different than Windows.
*
* @throws IOException
* @throws URISyntaxException
*/
public static void setExtDirInPHPIniFile() {
final IExtensionRegistry registry = Platform.getExtensionRegistry();
final IConfigurationElement[] elements = registry.getConfigurationElementsFor("org.eclipse.php.debug.core",
EXTENSION_POINT_NAME);
Matcher matcher = null;
Pattern pattern = null;
for (final IConfigurationElement element : elements) {
if (PHPEXE_TAG.equals(element.getName())) {
try {
String location = substitudeVariables(element.getAttribute(LOCATION_ATTRIBUTE));
final String pluginId = element.getDeclaringExtension().getNamespaceIdentifier();
location = location.substring(0, location.lastIndexOf("/")) //$NON-NLS-1$
.concat("/php.ini"); //$NON-NLS-1$
File phpIni = getFileFromLocation(location, pluginId);
if (phpIni == null)
continue;
File phpIniDir = phpIni.getParentFile();
String fileName = phpIni.getAbsolutePath();
String iniContent = readFile(fileName);
String correctDir = phpIniDir.getAbsolutePath().concat(File.separator).replaceAll("\\\\",
"\\\\\\\\");
pattern = Pattern.compile("(extension_dir=.*)(ext[^\\s]*)");
matcher = pattern.matcher(iniContent);
if (matcher.find()) {
Path iniPath = new Path(matcher.group(1).replaceAll("extension_dir=", ""));
Path currentPath = new Path(correctDir);
if (!currentPath.isPrefixOf(iniPath)) {
iniContent = iniContent.replaceAll(matcher.group(0).replaceAll("\\\\", "\\\\\\\\"),
"extension_dir=\"" + correctDir
+ matcher.group(2).replace("\"", "").replaceAll("\\\\", "\\\\\\\\")
+ "\"");
pattern = Pattern.compile("(zend_extension=.*)(ext[^\\s]ZendDebugger[^\\s]*)");
matcher = pattern.matcher(iniContent);
if (matcher.find()) {
iniContent = iniContent.replaceAll(matcher.group(0).replaceAll("\\\\", "\\\\\\\\"),
"zend_extension=\"" + correctDir
+ matcher.group(2).replace("\"", "").replaceAll("\\\\", "\\\\\\\\")
+ "\"");
}
pattern = Pattern.compile("(zend_extension=.*)(ext[^\\s]xdebug[^\\s]*)");
matcher = pattern.matcher(iniContent);
if (matcher.find()) {
iniContent = iniContent.replaceAll(matcher.group(0).replaceAll("\\\\", "\\\\\\\\"),
"zend_extension=\"" + correctDir
+ matcher.group(2).replace("\"", "").replaceAll("\\\\", "\\\\\\\\")
+ "\"");
}
pattern = Pattern.compile("(openssl.cafile=.*)(ca-bundle.crt[^\\s]*)");
matcher = pattern.matcher(iniContent);
if (matcher.find()) {
iniContent = iniContent.replaceAll(matcher.group(0).replaceAll("\\\\", "\\\\\\\\"),
"openssl.cafile=\"" + correctDir + matcher.group(2).replace("\"", "")
+ "\"");
}
BufferedWriter writer = new BufferedWriter(new FileWriter(fileName));
writer.write(iniContent);
writer.flush();
writer.close();
}
}
} catch (Exception ex) {
log(ex);
}
}
}
}
private static String substitudeVariables(String expression) throws CoreException {
return VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(expression, true);
}
private static File getFileFromLocation(String location, String pluginId) throws IOException {
if (Paths.get(location).isAbsolute()) {
return new File(location);
} else {
URL url = FileLocator.find(Platform.getBundle(pluginId), new Path(location), null);
if (url != null) {
url = FileLocator.resolve(url);
String filename = url.getFile();
return new File(filename);
}
}
return null;
}
private static String readFile(String file) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
String line = null;
StringBuilder stringBuilder = new StringBuilder();
String ls = System.getProperty("line.separator");
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
stringBuilder.append(ls);
}
return stringBuilder.toString();
}
}
}
public static final String ID = "org.eclipse.php.debug.core"; //$NON-NLS-1$
public static final int INTERNAL_ERROR = 10001;
public static final int INTERNAL_WARNING = 10002;
// The shared instance.
private static PHPDebugPlugin plugin;
private static boolean fIsSupportingMultipleDebugAllPages = true;
private boolean fInitialAutoRemoveLaunches;
private static boolean fLaunchChangedAutoRemoveLaunches;
/**
* The constructor.
*/
public PHPDebugPlugin() {
plugin = this;
getBundle().getBundleContext().addBundleListener(new PostStart());
}
public static final boolean DEBUG = Boolean.valueOf(Platform.getDebugOption("org.eclipse.php.debug.core/debug")) //$NON-NLS-1$
.booleanValue();
/**
* This method is called upon plug-in activation
*/
public void start(BundleContext context) throws Exception {
super.start(context);
ExtDirSupport.setExtDirInPHPIniFile();
}
/**
* This method is called when the plug-in is stopped
*/
public void stop(BundleContext context) throws Exception {
XDebugLaunchListener.shutdown();
DBGpProxyHandlersManager.INSTANCE.shutdown();
InstanceScope.INSTANCE.getNode(ID).flush();
DebuggerSettingsManager.INSTANCE.shutdown();
super.stop(context);
plugin = null;
DebugUIPlugin.getDefault().getPreferenceStore().setValue(IDebugUIConstants.PREF_AUTO_REMOVE_OLD_LAUNCHES,
fInitialAutoRemoveLaunches);
}
/**
* Returns the shared instance.
*/
public static PHPDebugPlugin getDefault() {
return plugin;
}
/**
* Returns the PHP debug ID.
*/
public static String getID() {
return IPHPDebugConstants.ID_PHP_DEBUG_CORE;
}
public static boolean getStopAtFirstLine() {
return Platform.getPreferencesService().getBoolean(PHPDebugPlugin.ID,
PHPDebugCorePreferenceNames.STOP_AT_FIRST_LINE, false, null);
}
public static boolean getDebugInfoOption() {
return Platform.getPreferencesService().getBoolean(PHPDebugPlugin.ID,
PHPDebugCorePreferenceNames.RUN_WITH_DEBUG_INFO, false, null);
}
public static boolean getOpenInBrowserOption() {
return Platform.getPreferencesService().getBoolean(PHPDebugPlugin.ID,
PHPDebugCorePreferenceNames.OPEN_IN_BROWSER, false, null);
}
/**
* Return default debugger id.
*
* @return default debugger id
*/
public static String getCurrentDebuggerId() {
// For backward compatibility try to get default debugger from
// preferences
String id = Platform.getPreferencesService().getString(PHPDebugPlugin.ID,
PHPDebugCorePreferenceNames.PHP_DEBUGGER_ID, null, null);
if (id == null || id.isEmpty()) {
return DebuggerCommunicationDaemon.ZEND_DEBUGGER_ID;
}
return id;
}
/**
* Return id of debugger associated with specified server.
*
* @param serverName
* @return debugger id
*/
public static String getDebuggerId(String serverName) {
if (serverName != null) {
Server server = ServersManager.getServer(serverName);
if (server != null) {
String serverDebuggerId = server.getDebuggerId();
if (serverDebuggerId != null) {
return serverDebuggerId;
}
}
}
return PHPDebugPlugin.getCurrentDebuggerId();
}
public static boolean getOpenDebugViewsOption() {
return Platform.getPreferencesService().getBoolean(PHPDebugPlugin.ID,
PHPDebugCorePreferenceNames.OPEN_DEBUG_VIEWS, false, null);
}
/**
* Returns the debugger port for the given debugger id. Return -1 if the
* debuggerId does not exist, or the debugger does not have a debug port.
*
* @param debuggerId
* @return The debug port, or -1.
*/
public static int getDebugPort(String debuggerId) {
IDebuggerConfiguration debuggerConfiguration = PHPDebuggersRegistry.getDebuggerConfiguration(debuggerId);
if (debuggerConfiguration == null) {
return -1;
}
return debuggerConfiguration.getPort();
}
/**
* Returns debug hosts
*
* @return debug hosts suitable for URL parameter
*/
public static String getDebugHosts() {
return Platform.getPreferencesService().getString(PHPDebugPlugin.ID, PHPDebugCorePreferenceNames.CLIENT_IP,
null, null);
}
public static String getWorkspaceDefaultServer() {
return org.eclipse.php.internal.server.core.Activator.getWorkspaceDefaultServer();
}
public static void log(IStatus status) {
try {
getDefault().getLog().log(status);
} catch (Exception e) {
}
}
public static void log(Throwable e) {
log(new Status(IStatus.ERROR, ID, INTERNAL_ERROR, "PHPDebug plugin internal error", e)); //$NON-NLS-1$
}
public static void logErrorMessage(String message) {
log(new Status(IStatus.ERROR, ID, INTERNAL_ERROR, message, null));
}
public static void logWarningMessage(String message) {
log(new Status(IStatus.WARNING, ID, INTERNAL_WARNING, message, null));
}
/**
* Returns if multiple sessions of debug launches are allowed when one of
* the launches contains a 'debug all pages' attribute.
*
* @return True, the multiple sessions are allowed; False, otherwise.
*/
public static boolean supportsMultipleDebugAllPages() {
return fIsSupportingMultipleDebugAllPages;
}
/**
* Allow or disallow the multiple debug sessions that has a launch attribute
* of 'debug all pages'.
*
* @param supported
*/
public static void setMultipleDebugAllPages(boolean supported) {
fIsSupportingMultipleDebugAllPages = supported;
}
//
// /**
// * Returns true if the auto remove launches was disabled by a PHP launch.
// * The auto remove flag is usually disabled when a PHP server launch was
// triggered and a
// * 'debug all pages' flag was on.
// * Note that this method will return true only if a php launch set it and
// the debug preferences has a 'true'
// * value for IDebugUIConstants.PREF_AUTO_REMOVE_OLD_LAUNCHES.
// *
// * @return True iff the auto remove old launches was disabled.
// */
// public static boolean isDisablingAutoRemoveLaunches() {
// return fDisableAutoRemoveLaunches;
// }
/**
* Enable or disable the auto remove old launches flag. The auto remove flag
* is usually disabled when a PHP server launch was triggered and a 'debug
* all pages' flag was on. Note that this method actually sets the
* IDebugUIConstants.PREF_AUTO_REMOVE_OLD_LAUNCHES preferences key for the
* {@link DebugUIPlugin}.
*
* @param disableAutoRemoveLaunches
*/
public static void setDisableAutoRemoveLaunches(boolean disableAutoRemoveLaunches) {
if (DebugUIPlugin.getDefault().getPreferenceStore()
.getBoolean(IDebugUIConstants.PREF_AUTO_REMOVE_OLD_LAUNCHES) == disableAutoRemoveLaunches) {
fLaunchChangedAutoRemoveLaunches = true;
DebugUIPlugin.getDefault().getPreferenceStore().setValue(IDebugUIConstants.PREF_AUTO_REMOVE_OLD_LAUNCHES,
!disableAutoRemoveLaunches);
}
}
/**
* Returns the initial value of the auto-remove-old launches.
*
* @return
*/
public boolean getInitialAutoRemoveLaunches() {
return fInitialAutoRemoveLaunches;
}
/**
* Get active debug target
*/
public static IDebugTarget getActiveDebugTarget() {
IDebugTarget debugTarget = null;
IAdaptable adaptable = DebugUITools.getDebugContext();
if (adaptable != null) {
IDebugElement element = (IDebugElement) adaptable.getAdapter(IDebugElement.class);
if (element != null) {
debugTarget = element.getDebugTarget();
}
}
if (debugTarget == null) {
IProcess process = DebugUITools.getCurrentProcess();
if (process instanceof PHPProcess) {
debugTarget = ((PHPProcess) process).getDebugTarget();
}
}
return debugTarget;
}
/**
* Get active remote debugger
*/
public static IRemoteDebugger getActiveRemoteDebugger() {
IDebugTarget debugTarget = getActiveDebugTarget();
if (debugTarget != null && debugTarget instanceof PHPDebugTarget) {
PHPDebugTarget phpDebugTarget = (PHPDebugTarget) debugTarget;
return phpDebugTarget.getRemoteDebugger();
}
return null;
}
//
private class AutoRemoveOldLaunchesListener implements IPropertyChangeListener {
public void propertyChange(PropertyChangeEvent event) {
if (IDebugUIConstants.PREF_AUTO_REMOVE_OLD_LAUNCHES.equals(event.getProperty())) {
if (fLaunchChangedAutoRemoveLaunches) {
fLaunchChangedAutoRemoveLaunches = false;// We got the
// event, so
// reset the
// flag.
} else {
// The event was triggered from some other source - e.g. The
// user changed the preferences manually.
fInitialAutoRemoveLaunches = Boolean.valueOf(event.getNewValue().toString());
}
}
}
}
public static String getCurrentDebuggerId(IProject project) {
if (project != null) {
PHPVersion phpVersion = ProjectOptions.getPHPVersion(project);
if (phpVersion != null) {
return getCurrentDebuggerId(phpVersion);
}
}
return getCurrentDebuggerId();
}
public static String getCurrentDebuggerId(PHPVersion phpVersion) {
PHPexeItem item = PHPexes.getInstance().getDefaultItemForPHPVersion(phpVersion);
if (item != null) {
return item.getDebuggerID();
}
return getCurrentDebuggerId();
}
public static PHPexeItem getPHPexeItem(IProject project) {
if (project != null) {
IEclipsePreferences node = createPreferenceScopes(project)[0]
.getNode(PHPProjectPreferences.getPreferenceNodeQualifier());
if (node != null) {
// Replace the workspace defaults with the project-specific
// settings.
String phpExe = node.get(PHPDebugCorePreferenceNames.DEFAULT_PHP, null);
if (phpExe != null) {
return PHPexes.getInstance().getItem(phpExe);
}
}
PHPVersion phpVersion = ProjectOptions.getPHPVersion(project);
if (phpVersion != null) {
return getPHPexeItem(phpVersion);
}
}
return getWorkspaceDefaultExe();
}
public static PHPexeItem getPHPexeItem(PHPVersion phpVersion) {
PHPexeItem item = PHPexes.getInstance().getDefaultItemForPHPVersion(phpVersion);
if (item != null) {
return item;
}
return getWorkspaceDefaultExe();
}
public static PHPexeItem getWorkspaceDefaultExe() {
IEclipsePreferences prefs = InstanceScope.INSTANCE.getNode(PHPDebugPlugin.ID);
if (prefs != null) {
String exeName = prefs.get(PHPDebugCorePreferenceNames.DEFAULT_PHP, null);
if (exeName != null && !exeName.isEmpty()) {
return PHPexes.getInstance().getItem(exeName);
}
}
return null;
}
// Creates a preferences scope for the given project.
// This scope will be used to search for preferences values.
public static IScopeContext[] createPreferenceScopes(IProject project) {
if (project != null) {
return new IScopeContext[] { new ProjectScope(project), InstanceScope.INSTANCE, DefaultScope.INSTANCE };
}
return new IScopeContext[] { InstanceScope.INSTANCE, DefaultScope.INSTANCE };
}
/**
* Returns if the current file name is actually a dummy file.
*/
public static boolean isDummyFile(String fileName) {
if (fileName != null) {
String dummyFile = Platform.getPreferencesService().getString(PHPDebugPlugin.ID,
PHPDebugCorePreferenceNames.ZEND_DEBUG_DUMMY_FILE, "", //$NON-NLS-1$
null);
int idx = Math.max(dummyFile.lastIndexOf('/'), dummyFile.lastIndexOf('\\'));
if (idx != -1) {
dummyFile = dummyFile.substring(idx + 1);
}
idx = Math.max(fileName.lastIndexOf('/'), fileName.lastIndexOf('\\'));
if (idx != -1) {
fileName = fileName.substring(idx + 1); // strip everything but
// last segment
}
return fileName.equals(dummyFile);
}
return false;
}
public static IEclipsePreferences getInstancePreferences() {
return InstanceScope.INSTANCE.getNode(ID);
}
public static IEclipsePreferences getDefaultPreferences() {
return DefaultScope.INSTANCE.getNode(ID);
}
}