PluginProbe
UpStream: a Project Management Plugin for WordPress / 1.39.2
UpStream: a Project Management Plugin for WordPress v1.39.2
trunk 1.39.0 1.39.1 1.39.2 1.39.3 2.0.7 2.1.0
← All changes | upstream.php +624 -20 trunk1.39.2 View file →
@@ -1,20 +1,624 @@
1 -<?php
2 -/**
3 - * Plugin Name: UpStream
4 - * Description: A Project Management Plugin for WordPress
5 - * Author: UpStream
6 - * Author URI: https://upstreamplugin.com
7 - * Version: 2.1.1
8 - * Text Domain: upstream
9 - * Domain Path: /languages
10 - *
11 - * @package UpStream
12 - */
13 -
14 -if ( ! defined( 'ABSPATH' ) ) {
15 - exit;
16 -}
17 -
18 -define( 'UPSTREAM_PLUGIN_FILE', __FILE__ );
19 -
20 -require_once 'class-upstream.php';
1 +<?php
2 +/**
3 + * Plugin Name: UpStream
4 + * Description: A WordPress Project Management plugin by UpStream.
5 + * Author: UpStream
6 + * Author URI: https://upstreamplugin.com
7 + * Version: 1.39.2
8 + * Text Domain: upstream
9 + * Domain Path: /languages
10 + */
11 +
12 +use UpStream\Comments;
13 +
14 +if ( ! defined('ABSPATH')) {
15 + exit;
16 +}
17 +
18 +if ( ! class_exists('UpStream')) :
19 +
20 + /**
21 + * Main UpStream Class.
22 + *
23 + * @since 1.0.0
24 + */
25 + final class UpStream
26 + {
27 + /**
28 + * @var UpStream The one true UpStream
29 + * @since 1.0.0
30 + */
31 + protected static $_instance = null;
32 +
33 + /**
34 + * @var Twig_Environment
35 + */
36 + protected $twig;
37 +
38 + /**
39 + * @var Container
40 + */
41 + protected $container;
42 +
43 + /**
44 + * Main UpStream Instance.
45 + */
46 + public static function instance()
47 + {
48 + if (is_null(self::$_instance)) {
49 + self::$_instance = new self();
50 + }
51 +
52 + return self::$_instance;
53 + }
54 +
55 + /**
56 + * Throw error on object clone.
57 + *
58 + * The whole idea of the singleton design pattern is that there is a single
59 + * object therefore, we don't want the object to be cloned.
60 + *
61 + * @since 1.0.0
62 + */
63 + public function __clone()
64 + {
65 + _doing_it_wrong(__FUNCTION__, 'You\'re not supposed to clone this class.', UPSTREAM_VERSION);
66 + }
67 +
68 + /**
69 + * Disable unserializing of the class.
70 + *
71 + * @since 1.0.0
72 + */
73 + public function __wakeup()
74 + {
75 + _doing_it_wrong(__FUNCTION__, 'You\'re not supposed to unserialize this class.', UPSTREAM_VERSION);
76 + }
77 +
78 + /**
79 + * Prevent the class instance being serialized.
80 + *
81 + * @since 1.10.2
82 + */
83 + public function __sleep()
84 + {
85 + _doing_it_wrong(__FUNCTION__, 'You\'re not supposed to serialize this class.', UPSTREAM_VERSION);
86 + }
87 +
88 + public function __construct()
89 + {
90 + $this->define_constants();
91 + $this->includes();
92 +
93 + $this->container = Container::get_instance();
94 +
95 + $this->init_framework();
96 +
97 + if (UpStream_Debug::is_enabled()) {
98 + UpStream_Debug::init();
99 + }
100 +
101 + $this->init_hooks();
102 +
103 + do_action('upstream_loaded');
104 + }
105 +
106 + /**
107 + * Hook into actions and filters.
108 + *
109 + * @since 1.0.0
110 + */
111 + private function init_hooks()
112 + {
113 + add_action('init', [$this, 'init']);
114 + add_filter('plugin_row_meta', [$this, 'plugin_row_meta'], 10, 2);
115 + add_filter('plugin_action_links_upstream/upstream.php', [$this, 'handleActionLinks']);
116 + add_filter('http_request_host_is_external', ['UpStream', 'allowExternalUpdateHost'], 10, 3);
117 + add_filter('quicktags_settings', 'upstream_tinymce_quicktags_settings');
118 + add_filter('tiny_mce_before_init', 'upstream_tinymce_before_init_setup_toolbar');
119 + add_filter('tiny_mce_before_init', 'upstream_tinymce_before_init');
120 + add_filter('teeny_mce_before_init', 'upstream_tinymce_before_init_setup_toolbar');
121 + add_filter('comments_clauses', [$this, 'filterCommentsOnDashboard'], 10, 2);
122 + add_filter('views_dashboard', ['UpStream_Admin', 'commentStatusLinks'], 10, 1);
123 + add_action('plugins_loaded', [$this, 'load_plugin_textdomain']);
124 +
125 + if (is_admin()) {
126 + add_action('admin_init', [$this->container['reviews'], 'init']);
127 + }
128 +
129 + global $pagenow;
130 + if ($pagenow === "plugins.php") {
131 + add_action(
132 + 'in_plugin_update_message-' . UPSTREAM_PLUGIN_BASENAME,
133 + [$this, 'renderAdditionalUpdateInfo'],
134 + 20,
135 + 2
136 + );
137 + }
138 + }
139 +
140 + /**
141 + * Initialize the Alledia Framework.
142 + */
143 + private function init_framework()
144 + {
145 + $this->container['framework']->init();
146 + }
147 +
148 +
149 + /**
150 + * Prevent a Client User from accessing any page other than the profile.
151 + *
152 + * @since 1.11.0
153 + *
154 + * @global $pagenow
155 + */
156 + public function limitClientUsersAdminAccess()
157 + {
158 + global $pagenow;
159 +
160 + $profilePage = 'profile.php';
161 + if ($pagenow !== $profilePage && $pagenow !== "edit.php" && ! wp_doing_ajax()) {
162 + wp_redirect(admin_url($profilePage));
163 + exit;
164 + }
165 + }
166 +
167 + /**
168 + * Make sure Client Users can only see the Profile menu item.
169 + *
170 + * @since 1.11.0
171 + *
172 + * @global $menu
173 + */
174 + public function limitClientUsersMenu()
175 + {
176 + global $menu;
177 +
178 + foreach ($menu as $menuIndex => $menuData) {
179 + $menuFile = isset($menuData[2]) ? $menuData[2] : null;
180 + if ($menuFile !== null) {
181 + if ($menuFile === 'profile.php' || $menuFile === 'edit.php?post_type=project') {
182 + continue;
183 + }
184 +
185 + remove_menu_page($menuFile);
186 + }
187 + }
188 + }
189 +
190 + /**
191 + * Hide some toolbar items from Client Users.
192 + *
193 + * @param \WP_Admin_Bar $wp_admin_bar
194 + *
195 + * @since 1.11.0
196 + *
197 + */
198 + public function limitClientUsersToolbarItems($wp_admin_bar)
199 + {
200 + $user = wp_get_current_user();
201 + $userRoles = (array)$user->roles;
202 +
203 + if (count(array_intersect(
204 + $userRoles,
205 + ['administrator', 'upstream_manager']
206 + )) === 0 && in_array(
207 + 'upstream_client_user',
208 + $userRoles
209 + )) {
210 + $menuItems = ['about', 'comments', 'new-content'];
211 +
212 + if ( ! is_admin()) {
213 + $menuItems = array_merge($menuItems, ['dashboard', 'edit']);
214 + }
215 +
216 + foreach ($menuItems as $menuItem) {
217 + $wp_admin_bar->remove_menu($menuItem);
218 + }
219 + }
220 + }
221 +
222 + /**
223 + * @return Container
224 + */
225 + public function get_container()
226 + {
227 + return $this->container;
228 + }
229 +
230 + /**
231 + * Define Constants.
232 + *
233 + * @since 1.0.0
234 + */
235 + private function define_constants()
236 + {
237 + $upload_dir = wp_upload_dir();
238 + $this->define('UPSTREAM_PLUGIN_FILE', __FILE__);
239 + $this->define('UPSTREAM_PLUGIN_DIR', plugin_dir_path(__FILE__));
240 + $this->define('UPSTREAM_PLUGIN_URL', plugin_dir_url(__FILE__));
241 + $this->define('UPSTREAM_PLUGIN_BASENAME', plugin_basename(__FILE__));
242 + $this->define('UPSTREAM_PLUGIN_RELATIVE_PATH', 'upstream');
243 +
244 + include_once __DIR__ . '/includes.php';
245 + }
246 +
247 + /**
248 + * Define constant if not already set.
249 + *
250 + * @param string $name
251 + * @param string|bool $value
252 + *
253 + * @since 1.0.0
254 + *
255 + */
256 + private function define($name, $value)
257 + {
258 + if ( ! defined($name)) {
259 + define($name, $value);
260 + }
261 + }
262 +
263 + /**
264 + * What type of request is this?
265 + * string $type frontend or admin.
266 + *
267 + * @return bool
268 + * @since 1.0.0
269 + */
270 + private function is_request($type)
271 + {
272 + switch ($type) {
273 + case 'admin':
274 + return is_admin();
275 + case 'frontend':
276 + return ( ! is_admin() || defined('DOING_AJAX')) && ! defined('DOING_CRON');
277 + }
278 + }
279 +
280 + /**
281 + * Include required core files used in admin and on the frontend.
282 + *
283 + * @since 1.0.0
284 + */
285 + public function includes()
286 + {
287 +
288 + if (file_exists(__DIR__ . '/vendor/autoload.php')) {
289 + require_once __DIR__ . '/vendor/autoload.php';
290 + }
291 +
292 + include_once __DIR__ . '/includes/class-up-exception.php';
293 + include_once __DIR__ . '/includes/trait-up-singleton.php';
294 + include_once __DIR__ . '/includes/trait-up-post-metadata.php';
295 + include_once __DIR__ . '/includes/abs-class-up-struct.php';
296 + include_once __DIR__ . '/includes/class-up-debug.php';
297 + include_once __DIR__ . '/includes/class-up-container.php';
298 + include_once __DIR__ . '/includes/up-install.php';
299 + include_once __DIR__ . '/includes/class-up-autoloader.php';
300 + include_once __DIR__ . '/includes/class-up-roles.php';
301 + include_once __DIR__ . '/includes/class-up-counts.php';
302 + include_once __DIR__ . '/includes/class-up-counter.php';
303 + include_once __DIR__ . '/includes/class-up-project-activity.php';
304 + include_once __DIR__ . '/includes/up-permalinks.php';
305 + include_once __DIR__ . '/includes/up-general-functions.php';
306 + include_once __DIR__ . '/includes/up-post-types.php';
307 + include_once __DIR__ . '/includes/up-labels.php';
308 + include_once __DIR__ . '/includes/class-up-milestones.php';
309 + include_once __DIR__ . '/includes/class-up-milestone.php';
310 + include_once __DIR__ . '/includes/class-up-factory.php';
311 + include_once __DIR__ . '/includes/up-install.php';
312 + include_once __DIR__ . '/includes/up-filesystem.php';
313 +
314 + if ($this->is_request('admin')) {
315 + global $pagenow;
316 +
317 + $isMultisite = (bool)is_multisite();
318 + $loadCmb2 = false;
319 +
320 + if ($isMultisite) {
321 + $currentPage = isset($_SERVER['PHP_SELF']) ? preg_replace(
322 + '/^\/wp-admin\//i',
323 + '',
324 + $_SERVER['PHP_SELF']
325 + ) : '';
326 + } else {
327 + $currentPage = (string)$pagenow;
328 + }
329 +
330 + if (in_array($currentPage, ['post.php', 'post-new.php'])) {
331 + $postType = isset($_REQUEST['post_type']) ? sanitize_text_field($_REQUEST['post_type']) : null;
332 + if (empty($postType)) {
333 + $projectId = isset($_REQUEST['post']) ? (int)$_REQUEST['post'] : 0;
334 + $postType = get_post_type($projectId);
335 + }
336 +
337 + if ( ! empty($postType)) {
338 + $postTypesUsingCmb2 = apply_filters(
339 + 'upstream:post_types_using_cmb2',
340 + ['project', 'client']
341 + );
342 + $loadCmb2 = in_array($postType, $postTypesUsingCmb2);
343 + }
344 + } elseif ($currentPage === 'admin.php'
345 + && isset($_REQUEST['page'])
346 + && preg_match('/^upstream_/i', sanitize_text_field($_REQUEST['page']))
347 + ) {
348 + $loadCmb2 = true;
349 + }
350 +
351 + if ($loadCmb2) {
352 + include_once __DIR__ . '/includes/libraries/cmb2/init.php';
353 + include_once __DIR__ . '/includes/libraries/cmb2-grid/Cmb2GridPlugin.php';
354 + }
355 +
356 + include_once __DIR__ . '/includes/admin/class-up-admin.php';
357 + include_once __DIR__ . '/includes/admin/class-up-admin-tasks-page.php';
358 + include_once __DIR__ . '/includes/admin/class-up-admin-bugs-page.php';
359 + include_once __DIR__ . '/includes/admin/class-up-admin-reviews.php';
360 + }
361 +
362 + if ($this->is_request('frontend')) {
363 + include_once __DIR__ . '/includes/frontend/class-up-template-loader.php';
364 + include_once __DIR__ . '/includes/frontend/class-up-login.php';
365 + include_once __DIR__ . '/includes/frontend/class-up-style-output.php';
366 + include_once __DIR__ . '/includes/frontend/up-enqueues.php';
367 + include_once __DIR__ . '/includes/frontend/up-template-functions.php';
368 + include_once __DIR__ . '/includes/frontend/up-table-functions.php';
369 + include_once __DIR__ . '/includes/frontend/class-up-view.php';
370 + include_once __DIR__ . '/includes/frontend/class-ajax.php';
371 + }
372 +
373 + include_once __DIR__ . '/includes/up-project-functions.php';
374 + include_once __DIR__ . '/includes/up-client-functions.php';
375 + include_once __DIR__ . '/includes/up-permissions-functions.php';
376 + include_once __DIR__ . '/includes/up-comments-migration.php';
377 + include_once __DIR__ . '/includes/class-up-comments.php';
378 + include_once __DIR__ . '/includes/class-up-comment.php';
379 + }
380 +
381 + /**
382 + * Init UpStream when WordPress Initialises.
383 + */
384 + public function init()
385 + {
386 + UpStream\Milestones::instantiate();
387 +
388 + do_action('before_upstream_init');
389 +
390 + $this->project = new UpStream_Project();
391 + $this->project_activity = new UpStream_Project_Activity();
392 +
393 + if (version_compare(PHP_VERSION, '5.5', '<')) {
394 + require_once UPSTREAM_PLUGIN_DIR . 'includes/libraries/password_compat-1.0.4/lib/password.php';
395 + }
396 +
397 + \UpStream\Migrations\Comments::run();
398 +
399 + $user = wp_get_current_user();
400 + $userRoles = (array)$user->roles;
401 + if (count(array_intersect(
402 + $userRoles,
403 + ['administrator', 'upstream_manager']
404 + )) === 0 && in_array(
405 + 'upstream_client_user',
406 + $userRoles
407 + )) {
408 + add_filter('admin_init', [$this, 'limitClientUsersAdminAccess']);
409 + add_filter('admin_head', [$this, 'limitClientUsersMenu']);
410 + add_action('admin_bar_menu', [$this, 'limitClientUsersToolbarItems'], 999);
411 + }
412 +
413 + $editOtherProjectsPermissionWereRemoved = (bool)get_option('upstream:role_upstream_users:drop_edit_others_projects');
414 + if ( ! $editOtherProjectsPermissionWereRemoved) {
415 + $role = get_role('upstream_user');
416 + if ($role)
417 + $role->remove_cap('edit_others_projects');
418 + unset($role);
419 +
420 + update_option('upstream:role_upstream_users:drop_edit_others_projects', 1);
421 + }
422 +
423 + UpStream_Options_Projects::createProjectsStatusesIds();
424 + UpStream_Options_Tasks::createTasksStatusesIds();
425 + UpStream_Options_Bugs::createBugsStatusesIds();
426 +
427 + Comments::instantiate();
428 +
429 + if ($this->is_request('frontend')) {
430 + UpStream_Ajax::instantiate();
431 + }
432 +
433 + do_action('upstream_init');
434 + }
435 +
436 + /**
437 + * Load Localisation files.
438 + */
439 + public function load_plugin_textdomain()
440 + {
441 + load_plugin_textdomain('upstream', false, UPSTREAM_PLUGIN_RELATIVE_PATH . '/languages/');
442 + }
443 +
444 +
445 + /**
446 + * Show row meta on the plugin screen.
447 + *
448 + * @param mixed $links Plugin Row Meta
449 + * @param mixed $file Plugin Base file
450 + *
451 + * @return array
452 + */
453 + public function plugin_row_meta($links, $file)
454 + {
455 + if ($file == UPSTREAM_PLUGIN_BASENAME) {
456 + $row_meta = [
457 + 'docs' => '<a href="' . esc_url('http://upstreamplugin.com/documentation') . '" title="' . esc_attr(__(
458 + 'View Documentation',
459 + 'upstream'
460 + )) . '">' . __('Docs', 'upstream') . '</a>',
461 + 'quick-start' => '<a href="' . esc_url('http://upstreamplugin.com/quick-start-guide') . '" title="' . esc_attr(__(
462 + 'View Quick Start Guide',
463 + 'upstream'
464 + )) . '">' . __('Quick Start Guide', 'upstream') . '</a>',
465 + ];
466 +
467 + return array_merge($links, $row_meta);
468 + }
469 +
470 + return (array)$links;
471 + }
472 +
473 + /**
474 + * Callback called to setup the links to display on the plugins page, besides active/deactivate links.
475 + *
476 + * @param array $links The list of links to be displayed.
477 + *
478 + * @return array
479 + * @since 1.11.1
480 + * @static
481 + *
482 + */
483 + public static function handleActionLinks($links)
484 + {
485 + $links['settings'] = sprintf(
486 + '<a href="%s" title="%2$s" aria-label="%2$s">%3$s</a>',
487 + esc_url(admin_url('admin.php?page=upstream_general')),
488 + esc_html__('Open Settings Page', 'upstream'),
489 + esc_html__('Settings', 'upstream')
490 + );
491 +
492 + return $links;
493 + }
494 +
495 + /**
496 + * Ensures the plugins update API's host is whitelisted to WordPress external requests.
497 + *
498 + * @param boolean $isAllowed
499 + * @param string $host
500 + * @param string $url
501 + *
502 + * @return boolean
503 + * @since 1.11.1
504 + * @static
505 +
506 +
507 +
508 +
509 + *
510 + */
511 + public static function allowExternalUpdateHost($isAllowed, $host, $url)
512 + {
513 + if ($host === 'upstreamplugin.com') {
514 + return true;
515 + }
516 +
517 + return $isAllowed;
518 + }
519 +
520 + /**
521 + * Render additional update info if needed.
522 + *
523 + * @param array $pluginData Plugin metadata.
524 + * @param object $response Metadata about the available plugin update.
525 + *
526 + * @since 1.12.5
527 + * @static
528 + *
529 + * @see https://developer.wordpress.org/reference/hooks/in_plugin_update_message-file
530 + *
531 + */
532 + public static function renderAdditionalUpdateInfo($pluginData, $response)
533 + {
534 + $updateNoticeTitleHtml = sprintf(
535 + '<strong style="font-size: 1.25em; display: block; margin-top: 10px;">%s</strong>',
536 + esc_html__('Update notice:', 'upstream')
537 + );
538 +
539 + if (version_compare(UPSTREAM_VERSION, "1.12.5", "<")) {
540 + printf(
541 + $updateNoticeTitleHtml .
542 + _x(
543 + 'Starting from <strong>%s</strong> <code>%s</code> capability was removed from <code>%s</code> users role.',
544 + '1st %s: plugin version, 2nd %s: capability name, 3rd: UpStream User role',
545 + 'upstream'
546 + ),
547 + 'v1.12.5',
548 + 'edit_others_projects',
549 + esc_html__('UpStream User', 'upstream')
550 + );
551 + }
552 + }
553 +
554 + /**
555 + * Make sure Recent Comments section on admin Dashboard display only comments
556 + * current user is allowed to see from projects he's allowed to access.
557 + *
558 + * @param array $queryArgs Query clauses.
559 + * @param WP_Comment_Query $query Current query instance.
560 + *
561 + * @return array $queryArgs
562 + * @global $pagenow , $wpdb
563 + *
564 + * @since 1.13.0
565 + * @static
566 + *
567 + */
568 + public static function filterCommentsOnDashboard($queryArgs, $query)
569 + {
570 + global $pagenow;
571 +
572 + if (is_admin()
573 + && $pagenow === "index.php"
574 + && ! isUserEitherManagerOrAdmin()
575 + ) {
576 + global $wpdb;
577 +
578 + $queryArgs['join'] = 'LEFT JOIN ' . $wpdb->prefix . 'posts AS post ON post.ID = ' . $wpdb->prefix . 'comments.comment_post_ID';
579 +
580 + $user = wp_get_current_user();
581 + if (in_array('upstream_user', $user->roles) || in_array('upstream_client_user', $user->roles)) {
582 + $projects = (array)upstream_get_users_projects($user);
583 + if (count($projects) === 0) {
584 + $queryArgs['where'] = "(post.ID = -1)";
585 + } else {
586 + $queryArgs['where'] = "(post.post_type = 'project' AND post.ID IN (" . implode(
587 + ', ',
588 + array_keys($projects)
589 + ) . "))";
590 +
591 + $userCanModerateComments = user_can($user, 'moderate_comments');
592 + if ( ! $userCanModerateComments) {
593 + $queryArgs['where'] .= " AND ( comment_approved = '1' )";
594 + } else {
595 + $queryArgs['where'] .= " AND ( comment_approved = '1' OR comment_approved = '0' )";
596 + }
597 + }
598 + } else {
599 + $queryArgs['where'] .= " AND (post.post_type != 'project')";
600 + }
601 + }
602 +
603 + return $queryArgs;
604 + }
605 + }
606 +endif;
607 +
608 +
609 +/**
610 + * Main instance of UpStream.
611 + *
612 + * Returns the main instance of UpStream to prevent the need to use globals.
613 + *
614 + * @return UpStream
615 + * @since 1.0.0
616 + */
617 +function UpStream()
618 +{
619 + return UpStream::instance();
620 +}
621 +
622 +UpStream();
623 +
624 +do_action('upstream_run');