├── assets ├── css │ ├── _colors.scss │ ├── _notices.scss │ ├── blicki.css.map │ ├── _header.scss │ ├── blicki.scss │ ├── _contributors.scss │ ├── _toc.scss │ ├── _editor.scss │ ├── _revisions.scss │ └── blicki.css └── js │ ├── blicki.min.js │ └── blicki.js ├── uninstall.php ├── .gitignore ├── package.json ├── includes ├── class-blicki-shortcodes.php ├── blicki-core-functions.php ├── class-blicki-history.php ├── class-blicki-notices.php ├── class-blicki-submit-form.php ├── class-blicki-edit-form.php ├── class-blicki-diff-viewer.php ├── class-blicki-suggestion.php ├── class-blicki-cpt.php └── class-blicki-content.php ├── readme.txt ├── README.md ├── Gruntfile.js ├── blicki.php └── languages └── blicki.pot /assets/css/_colors.scss: -------------------------------------------------------------------------------- 1 | // Blicki Colors 2 | $white: #ffffff; 3 | $gray-light: #f5f5f5; 4 | $green-light: #e9ffe9; 5 | $red-light: #ffe9e9; -------------------------------------------------------------------------------- /uninstall.php: -------------------------------------------------------------------------------- 1 | query( "DROP TABLE IF EXISTS {$wpdb->prefix}blicky_history" ); 9 | -------------------------------------------------------------------------------- /assets/css/_notices.scss: -------------------------------------------------------------------------------- 1 | .blicki-notice { 2 | padding: 1em; 3 | margin: 0 0 1em 0; 4 | 5 | p { 6 | margin: 0; 7 | padding: 0; 8 | } 9 | } 10 | 11 | .blicki-notice--success { 12 | background: $green-light; 13 | } 14 | 15 | .blicki-notice--error { 16 | background: $red-light; 17 | } 18 | -------------------------------------------------------------------------------- /assets/css/blicki.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "mappings": "AAGA,aAAc;EACb,UAAU,ECHK,OAAO;;ADMvB,qBAAsB;EACrB,UAAU,ECNK,OAAO;EDOtB,UAAU,EAAE,GAAG;EACf,OAAO,EAAE,KAAK;EAEd,uCAAkB;IACjB,UAAU,ECZI,OAAO;;ADgBvB,oBAAqB;EACpB,MAAM,EAAE,YAAY", 4 | "sources": ["blicki.scss","_colors.scss"], 5 | "names": [], 6 | "file": "blicki.css" 7 | } 8 | -------------------------------------------------------------------------------- /assets/css/_header.scss: -------------------------------------------------------------------------------- 1 | // Blicki Header 2 | 3 | .blicki__header { 4 | display: flex; 5 | flex-wrap: wrap; 6 | justify-content: space-between; 7 | font-size: 0.9em; 8 | } 9 | 10 | .blicki__latest, 11 | .blicki__actions { 12 | margin-bottom: 15px; 13 | } 14 | 15 | .blicki__actions-history { 16 | margin-right: 15px; 17 | } -------------------------------------------------------------------------------- /assets/css/blicki.scss: -------------------------------------------------------------------------------- 1 | // Blicki! 2 | @import "_colors.scss"; 3 | @import "_header.scss"; 4 | @import "_editor.scss"; 5 | @import "_toc.scss"; 6 | @import "_notices.scss"; 7 | @import "_revisions.scss"; 8 | @import "_contributors.scss"; 9 | 10 | .blicky-edit, .blicky-history { 11 | display: none; 12 | } 13 | 14 | table.diff { 15 | border: 0; 16 | th { 17 | border: 0; 18 | } 19 | td { 20 | border: 0; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Editors 2 | project.xml 3 | project.properties 4 | /nbproject/private/ 5 | .buildpath 6 | .project 7 | .settings* 8 | .idea 9 | *.swp 10 | 11 | # Grunt 12 | /node_modules/ 13 | 14 | # Sass 15 | .sass-cache/ 16 | 17 | # OS X metadata 18 | .DS_Store 19 | 20 | # Windows junk 21 | Thumbs.db 22 | 23 | # ApiGen 24 | /wc-apidocs/ 25 | 26 | # Unit tests 27 | /tmp 28 | /tests/bin/tmp 29 | 30 | # Logs 31 | /logs 32 | 33 | # Composer 34 | /vendor/ 35 | -------------------------------------------------------------------------------- /assets/css/_contributors.scss: -------------------------------------------------------------------------------- 1 | .blicki__contributors-list { 2 | margin: 0; 3 | padding: 0; 4 | list-style: none; 5 | font-size: 0.9em; 6 | } 7 | 8 | .blicki__contributors-user { 9 | display: inline-block; 10 | margin: 0 1em 1em 0; 11 | padding-right: .5em; 12 | width: calc(33.33% - 2em/3); 13 | 14 | img, 15 | .blicki__contributors-user-name { 16 | display: inline-block; 17 | vertical-align: middle; 18 | } 19 | 20 | img { 21 | padding-right: .5em; 22 | width: 33%; 23 | } 24 | 25 | .blicki__contributors-user-name { 26 | margin: 0; 27 | text-overflow: ellipsis; 28 | overflow: hidden; 29 | white-space: nowrap; 30 | width: 66.66%; 31 | } 32 | 33 | @media screen and ( max-width: 680px ) { 34 | width: calc(50% - 1em); 35 | } 36 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blicki", 3 | "title": "Blicki", 4 | "version": "1.0.0", 5 | "homepage": "https://automattic.com/", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/Automattic/blicki.git" 9 | }, 10 | "license": "GPL-3.0+", 11 | "main": "Gruntfile.js", 12 | "devDependencies": { 13 | "grunt": "~1.0.1", 14 | "grunt-checktextdomain": "~1.0.0", 15 | "grunt-contrib-clean": "~1.0.0", 16 | "grunt-contrib-cssmin": "~1.0.0", 17 | "grunt-contrib-sass": "~1.0.0", 18 | "grunt-contrib-uglify": "~1.0.0", 19 | "grunt-contrib-watch": "~1.0.0", 20 | "grunt-wp-i18n": "~0.5.4", 21 | "node-bourbon": "~4.2.3" 22 | }, 23 | "engines": { 24 | "node": ">=0.8.0", 25 | "npm": ">=1.1.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /includes/class-blicki-shortcodes.php: -------------------------------------------------------------------------------- 1 | false, 12 | 'quicktags' => false, 13 | 'editor_height' => 400, 14 | 'tinymce' => array( 15 | 'toolbar1' => 'formatselect,bold,italic,strikethrough,separator,bullist,numlist,separator,blockquote,separator,justifyleft,justifycenter,justifyright,separator,link,unlink,separator,undo,redo,separator,', 16 | 'toolbar2' => '', 17 | ), 18 | ); 19 | 20 | wp_editor( 21 | htmlspecialchars_decode( $content ), 22 | $id, 23 | $settings 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /assets/css/_toc.scss: -------------------------------------------------------------------------------- 1 | .blicki__toc-container { 2 | background: rgba( #000, 0.05 ); 3 | margin: 1em 0; 4 | padding: 1em; 5 | width: 100%; 6 | 7 | ol { 8 | display: none; 9 | margin: 1em 0 0; 10 | } 11 | 12 | li { 13 | font-size: 0.8em; 14 | list-style: none; 15 | padding: 5px 0; 16 | 17 | &.toc-level2 { 18 | padding-left: 10px; 19 | } 20 | 21 | &.toc-level3 { 22 | padding-left: 20px; 23 | } 24 | 25 | &.toc-level4 { 26 | padding-left: 30px; 27 | } 28 | 29 | &.toc-level5 { 30 | padding-left: 40px; 31 | } 32 | 33 | &.toc-level6 { 34 | padding-left: 50px; 35 | } 36 | } 37 | 38 | @media all and ( max-width: 480px ) { 39 | margin: 0 0 2em; 40 | width: 100%; 41 | 42 | .blicki__toc { 43 | display: none; 44 | } 45 | } 46 | } 47 | 48 | .blicki__toc-toggle { 49 | float: right; 50 | margin-top: 5px; 51 | font-size: 0.7em; 52 | } -------------------------------------------------------------------------------- /assets/css/_editor.scss: -------------------------------------------------------------------------------- 1 | .blicki__edit { 2 | background: $white; 3 | } 4 | 5 | .blicki__edit-details { 6 | background: $gray-light; 7 | border: 1px solid darken( $gray-light, 10% ); 8 | margin-bottom: 2em; 9 | 10 | input[type=email] { 11 | background: $white; 12 | } 13 | label { 14 | display: block; 15 | } 16 | } 17 | 18 | .blicki__edit-details-editor { 19 | margin-bottom: 1em; 20 | } 21 | 22 | .blicki__edit-details-field, 23 | .blicki__edit-details-submit { 24 | padding: 0 1em 1em; 25 | 26 | input[type=text], 27 | input[type=email] { 28 | width: 100%; 29 | 30 | @media all and ( max-width: 480px ) { 31 | width: 100%; 32 | } 33 | } 34 | } 35 | 36 | .blicki__edit-details-editor, 37 | .blicki__edit-details-submit { 38 | border-bottom: 1px solid darken( $gray-light, 10% ); 39 | } 40 | 41 | .blicki__edit-details-submit { 42 | border-top: 1px solid darken( $gray-light, 10% ); 43 | } 44 | 45 | .blicki__edit-submit { 46 | margin: 1em 20px 0 0; 47 | } 48 | -------------------------------------------------------------------------------- /assets/css/_revisions.scss: -------------------------------------------------------------------------------- 1 | .blicky-history { 2 | background: $white; 3 | border: 1px solid darken( $gray-light, 10% ); 4 | margin-bottom: 2em; 5 | } 6 | 7 | .blicki__revision-heading { 8 | background: $gray-light; 9 | font-weight: bold; 10 | padding: 5px 15px; 11 | } 12 | 13 | .blicki__revision-list { 14 | list-style: none; 15 | margin: 0; 16 | padding: 0; 17 | } 18 | 19 | .blicki__revision-list-item { 20 | align-items: center; 21 | display: flex; 22 | flex-wrap: wrap; 23 | font-size: 0.8em; 24 | justify-content: space-between; 25 | padding: 10px 15px; 26 | 27 | &:not(:last-child) { 28 | border-bottom: 1px solid $gray-light; 29 | } 30 | } 31 | 32 | .blicki__revision-avatar { 33 | height: 18px; 34 | margin-right: 5px; 35 | width: 18px; 36 | } 37 | 38 | // diff viewer 39 | .blicki_page_blicki-show-diff, 40 | .type-blicki { 41 | .diff-title { 42 | display: block; 43 | } 44 | 45 | .diff-sub-title td:nth-child(1n+0), 46 | tbody td:nth-child(2) { 47 | display: none; 48 | } 49 | } -------------------------------------------------------------------------------- /assets/js/blicki.min.js: -------------------------------------------------------------------------------- 1 | jQuery(function(a){a(".blicki__actions-history").on("click",function(b){b.preventDefault(),a(".blicky-edit").hide(),a(".blicky-history").slideToggle(150)}),a(".blicki__actions-edit, .blicki__edit-cancel").on("click",function(b){b.preventDefault(),a(".blicky-history").hide(),a(".blicky-edit").slideToggle(150)}),a(".blicki__edit-cancel").on("click",function(b){b.preventDefault(),a(".blicky-edit").slideUp()}),a(document).on("click",".blicki__toc-toggle",function(b){b.preventDefault();var c=a(this),d=c.siblings(".blicki__toc");d.is(":visible")?(c.text("Show"),d.slideUp(150)):(c.text("Hide"),d.slideDown(150))}),a(document).on("click","ol.blicki__toc a",function(b){b.preventDefault();var c=a(this).attr("href");a("body").animate({scrollTop:a(c).offset().top},500)}),a(".blicky-entry-content").each(function(){var b=a(this),c=[],d=b.attr("id");if(a("h1, h2, h3, h4, h5, h6",b).each(function(b){switch(a(this).prop("nodeName").toLowerCase()){case"h1":c.push({level:1,text:a(this).text(),target:"#"+d+b});break;case"h2":c.push({level:2,text:a(this).text(),target:"#"+d+b});break;case"h3":c.push({level:3,text:a(this).text(),target:"#"+d+b});break;case"h4":c.push({level:4,text:a(this).text(),target:"#"+d+b});break;case"h5":c.push({level:5,text:a(this).text(),target:"#"+d+b});break;case"h6":c.push({level:6,text:a(this).text(),target:"#"+d+b})}a(this).attr("id",d+b)}),c.length){var e=0;c.forEach(function(a){(0===e||a.level'+blicki_js_params.toc+""),g=a('Show'),h=a('
    ');c.forEach(function(b){var c=a("
  1. ").addClass("toc-level"+b.level),d=a("").attr("href",b.target).text(b.text);c.append(d),h.append(c)});var i=a('
  2. '+blicki_js_params.contributors+"
  3. ");h.append(i),f.append(g,h),b.prepend(f)}})}); -------------------------------------------------------------------------------- /includes/class-blicki-history.php: -------------------------------------------------------------------------------- 1 | 0, 31 | 'revision_id' => 0, 32 | 'user_name' => '', 33 | 'user_email' => '', 34 | 'event_timestamp' => time(), 35 | ) ); 36 | 37 | if ( empty( $data['user_id'] ) && ( empty( $data['user_email'] ) || empty( $data['user_name'] ) ) ) { 38 | return; 39 | } 40 | 41 | $wpdb->insert( 42 | $wpdb->prefix . 'blicky_history', 43 | array( 44 | 'entry_id' => absint( $entry_id ), 45 | 'user_id' => $data['user_id'], 46 | 'revision_id' => $data['revision_id'], 47 | 'user_name' => $data['user_name'], 48 | 'user_email' => $data['user_email'], 49 | 'event' => $type, 50 | 'event_timestamp' => date( 'Y-m-d H:i:s', $data['event_timestamp'] ), 51 | ) 52 | ); 53 | } 54 | } 55 | 56 | /** 57 | * Get events. 58 | */ 59 | public static function get_events( $entry_id, $type = 'all' ) { 60 | global $wpdb; 61 | 62 | if ( 'all' === $type ) { 63 | $query = $wpdb->prepare( 64 | "SELECT * FROM {$wpdb->prefix}blicky_history WHERE entry_id = %d ORDER BY event_timestamp DESC", 65 | $entry_id 66 | ); 67 | } else { 68 | $query = $wpdb->prepare( 69 | "SELECT * FROM {$wpdb->prefix}blicky_history WHERE entry_id = %d AND event = %s ORDER BY event_timestamp DESC", 70 | $entry_id, 71 | $type 72 | ); 73 | } 74 | 75 | return $wpdb->get_results( $query ); 76 | } 77 | 78 | public static function get_event_display_name( $type ) { 79 | $event_display_names = array( 80 | 'submitted' => __( 'Submitted by', 'blicki' ), 81 | 'updated' => __( 'Updated by', 'blicki' ), 82 | 'contributed' => __( 'Contribution from', 'blicki'), 83 | ); 84 | 85 | if ( ! isset( $event_display_names[ $type ] ) ) { 86 | return __( 'Modified by', 'blicki' ); 87 | } 88 | 89 | return $event_display_names[ $type ]; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === Blicki === 2 | Contributors: benedictsinger, danhauk, graceofbase, lschuyler, mikejolley 3 | Tags: wiki 4 | Requires at least: 4.6.1 5 | Tested up to: 4.6.1 6 | Stable tag: 4.6.1 7 | 8 | A Knowledge Evolution Revolution: Contribute with ease. Watch how information changes over time. 9 | 10 | == Description == 11 | 12 | A Blicki is a blog and a wiki. The purpose is to create a platform on which anyone can share knowledge. The blicki makes it easy to contribute with a front-end blog editor, while showing how information is added to and evolved. 13 | 14 | 15 | == Features == 16 | 17 | * Front-end blog-style editor. No markdown required. 18 | * Anyone can edit. Visitors do not need to create an account to contribute. 19 | * An admin role has the ability to moderate new pages and edits. 20 | * Revision history and stats show at the footer of each blicki entry. 21 | * Links that connect to other entries on your Blicki. 22 | * Each entry has a table of contents. Hello easy navigation. 23 | * Images can be added via hyperlinks. Don’t worry about your Blicki getting weighed down with too much or oversized media. 24 | * The Blicki can be added to previously built sites via a custom post type. You can have a website, a blog, and a blicki all in one! 25 | 26 | 27 | 28 | == Installation == 29 | 30 | 1. Upload the plugin files to the `/wp-content/plugins/blicki` directory, or install the plugin through the WordPress plugins screen directly. 31 | 2. Activate the plugin through the 'Plugins' screen in WordPress 32 | 3. Use the Settings->Plugin Name screen to configure the plugin (?? not sure we’ll have this) 33 | 4. Your wiki content can be found by clicking ‘Wiki’ on your dashboard’s sidebar. 34 | 35 | 36 | == Frequently Asked Questions == 37 | 38 | = What if someone shares false knowledge or something that shouldn't be on the Blicki? = 39 | 40 | New entries will be submitted for moderation by an appointed admin. Edits to entries will send a notification to the admin to review. 41 | 42 | = Do I have to use the wiki markdown language to contribute? = 43 | 44 | Nah - we're adding a front-end blog editor, so you can leave the brackets behind. 45 | 46 | = Does the Blicki have a theme song? = 47 | 48 | Of course! That blicki. That blicki wiki. 49 | 50 | = Why is there a C in Blicki? = 51 | 52 | The 'C' stands for carrot. Also, bliki.com was registered in 1999 and is unavailable. 53 | 54 | = Can I set a blicki page as the front page of my site? = 55 | 56 | That setting isn't an option right now, and it's for a good reason. The front page of a wiki-style site is a great place to explain the purpose of the site and introduce navigation links around the blicki. 57 | 58 | Here's information on creating a static front page: 59 | 60 | https://codex.wordpress.org/Creating_a_Static_Front_Page 61 | 62 | 63 | 64 | 65 | == Changelog == 66 | 67 | = 0.1 = 68 | * Original version 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blicki 2 | **Contributors:** benedictsinger, danhauk, graceofbase, lschuyler, mikejolley 3 | **Tags:** wiki 4 | **Requires at least:** 4.6.1 5 | **Tested up to:** 4.6.1 6 | **Stable tag:** 4.6.1 7 | 8 | A Knowledge Evolution Revolution: Contribute with ease. Watch how information changes over time. 9 | 10 | 11 | ## Description 12 | 13 | A Blicki is a blog and a wiki. The purpose is to create a platform on which anyone can share knowledge. The blicki makes it easy to contribute with a front-end blog editor, while showing how information is added to and evolved. 14 | 15 | 16 | ### Features 17 | 18 | * Front-end blog-style editor. No markdown required. 19 | * Anyone can edit. Visitors do not need to create an account to contribute. 20 | * An admin role has the ability to moderate new pages and edits. 21 | * Revision history and stats show at the footer of each blicki entry. 22 | * Links that connect to other entries on your Blicki. 23 | * Each entry has a table of contents. Hello easy navigation. 24 | * Images can be added via hyperlinks. Don’t worry about your Blicki getting weighed down with too much or oversized media. 25 | * The Blicki can be added to previously built sites via a custom post type. You can have a website, a blog, and a blicki all in one! 26 | 27 | 28 | 29 | 30 | ## Installation 31 | 32 | 1. Upload the plugin files to the `/wp-content/plugins/blicki` directory, or install the plugin through the WordPress plugins screen directly. 33 | 2. Activate the plugin through the 'Plugins' screen in WordPress 34 | 3. Use the Settings->Plugin Name screen to configure the plugin (?? not sure we’ll have this) 35 | 4. Your wiki content can be found by clicking ‘Wiki’ on your dashboard’s sidebar. 36 | 37 | 38 | 39 | ## Frequently Asked Questions 40 | 41 | 42 | ### What if someone shares false knowledge or something that shouldn't be on the Blicki? 43 | 44 | New entries will be submitted for moderation by an appointed admin. Edits to entries will send a notification to the admin to review. 45 | 46 | 47 | ### Do I have to use the wiki markdown language to contribute? 48 | 49 | Nah - we're adding a front-end blog editor, so you can leave the brackets behind. 50 | 51 | 52 | ### Does the Blicki have a theme song? 53 | 54 | Of course! That blicki. That blicki wiki. 55 | 56 | 57 | ### Why is there a C in Blicki? 58 | 59 | The 'C' stands for carrot. Also, bliki.com was registered in 1999 and is unavailable. 60 | 61 | 62 | ### Can I set a blicki page as the front page of my site? 63 | 64 | That setting isn't an option right now, and it's for a good reason. The front page of a wiki-style site is a great place to explain the purpose of the site and introduce navigation links around the blicki. 65 | 66 | Here's information on creating a static front page: 67 | 68 | https://codex.wordpress.org/Creating_a_Static_Front_Page 69 | 70 | 71 | ## Changelog 72 | 73 | 74 | ### 0.1 75 | * Original version 76 | -------------------------------------------------------------------------------- /includes/class-blicki-notices.php: -------------------------------------------------------------------------------- 1 | $notice_text, 27 | 'type' => $type, 28 | ); 29 | } 30 | 31 | public static function has_error() { 32 | foreach ( self::$notices as $notice ) { 33 | if ( 'error' === $notice->type ) { 34 | return true; 35 | } 36 | } 37 | return false; 38 | } 39 | 40 | public static function display() { 41 | if ( ! empty( self::$notices ) ) { 42 | foreach ( self::$notices as $notice ) { 43 | echo '
    ' . wpautop( esc_html( $notice->text ) ) . '
    '; 44 | } 45 | } 46 | } 47 | 48 | public function add_pending_suggestions_notice() { 49 | if ( isset( $_GET['page'] ) && 'blicki-show-diff' === $_GET['page'] ) { 50 | // don't show this notice on the approval pages, that's silly 51 | return; 52 | } 53 | 54 | $suggestions = get_posts( array( 55 | 'fields' => 'ids', 56 | 'post_type' => 'blicki-suggestion', 57 | 'posts_per_page' => -1, 58 | 'post_status' => 'pending', 59 | ) ); 60 | 61 | $num = count( $suggestions ); 62 | if ( $num > 0 ) { 63 | ?> 64 |

    65 | " . __( 'Entries', 'blicki' ) . ""; 73 | ?> 74 |

    75 | 'ids', 84 | 'post_type' => 'blicki-suggestion', 85 | 'posts_per_page' => -1, 86 | 'post_status' => 'pending', 87 | ) ); 88 | 89 | $num = count( $suggestions ); 90 | 91 | if ( $num > 0 ) { 92 | foreach ( $menu as $prio => $menu_item ) { 93 | if ( 'Blicki' === $menu_item[0] ) { 94 | $menu[$prio][0] .= " " . $num . ""; 95 | break; 96 | } 97 | } 98 | } 99 | } 100 | } 101 | new Blicki_Notices(); 102 | -------------------------------------------------------------------------------- /assets/css/blicki.css: -------------------------------------------------------------------------------- 1 | .blicki__header{display:flex;flex-wrap:wrap;justify-content:space-between;font-size:.9em}.blicki__actions,.blicki__latest{margin-bottom:15px}.blicki__actions-history{margin-right:15px}.blicki__edit{background:#fff}.blicki__edit-details{background:#f5f5f5;border:1px solid #dcdcdc;margin-bottom:2em}.blicki__edit-details input[type=email]{background:#fff}.blicki__edit-details label{display:block}.blicki__edit-details-editor{margin-bottom:1em}.blicki__edit-details-field,.blicki__edit-details-submit{padding:0 1em 1em}.blicki__edit-details-field input[type=email],.blicki__edit-details-field input[type=text],.blicki__edit-details-submit input[type=email],.blicki__edit-details-submit input[type=text]{width:100%}.blicki__edit-details-editor,.blicki__edit-details-submit{border-bottom:1px solid #dcdcdc}.blicki__edit-details-submit{border-top:1px solid #dcdcdc}.blicki__edit-submit{margin:1em 20px 0 0}.blicki__toc-container{background:rgba(0,0,0,.05);margin:1em 0;padding:1em;width:100%}.blicki__toc-container ol{display:none;margin:1em 0 0}.blicki__toc-container li{font-size:.8em;list-style:none;padding:5px 0}.blicki__toc-container li.toc-level2{padding-left:10px}.blicki__toc-container li.toc-level3{padding-left:20px}.blicki__toc-container li.toc-level4{padding-left:30px}.blicki__toc-container li.toc-level5{padding-left:40px}.blicki__toc-container li.toc-level6{padding-left:50px}@media all and (max-width:480px){.blicki__edit-details-field input[type=email],.blicki__edit-details-field input[type=text],.blicki__edit-details-submit input[type=email],.blicki__edit-details-submit input[type=text]{width:100%}.blicki__toc-container{margin:0 0 2em;width:100%}.blicki__toc-container .blicki__toc{display:none}}.blicki__toc-toggle{float:right;margin-top:5px;font-size:.7em}.blicki-notice{padding:1em;margin:0 0 1em}.blicki-notice p{margin:0;padding:0}.blicki-notice--success{background:#e9ffe9}.blicki-notice--error{background:#ffe9e9}.blicky-history{background:#fff;border:1px solid #dcdcdc;margin-bottom:2em}.blicki__revision-heading{background:#f5f5f5;font-weight:700;padding:5px 15px}.blicki__revision-list{list-style:none;margin:0;padding:0}.blicki__revision-list-item{align-items:center;display:flex;flex-wrap:wrap;font-size:.8em;justify-content:space-between;padding:10px 15px}.blicki__revision-list-item:not(:last-child){border-bottom:1px solid #f5f5f5}.blicki__revision-avatar{height:18px;margin-right:5px;width:18px}.blicki_page_blicki-show-diff .diff-title,.type-blicki .diff-title{display:block}.blicki_page_blicki-show-diff .diff-sub-title td:nth-child(1n+0),.blicki_page_blicki-show-diff tbody td:nth-child(2),.type-blicki .diff-sub-title td:nth-child(1n+0),.type-blicki tbody td:nth-child(2){display:none}.blicki__contributors-list{margin:0;padding:0;list-style:none;font-size:.9em}.blicki__contributors-user{display:inline-block;margin:0 1em 1em 0;padding-right:.5em;width:calc(33.33% - 2em/3)}.blicki__contributors-user .blicki__contributors-user-name,.blicki__contributors-user img{display:inline-block;vertical-align:middle}.blicki__contributors-user img{padding-right:.5em;width:33%}.blicki__contributors-user .blicki__contributors-user-name{margin:0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;width:66.66%}@media screen and (max-width:680px){.blicki__contributors-user{width:calc(50% - 1em)}}.blicky-edit,.blicky-history{display:none}table.diff,table.diff td,table.diff th{border:0} -------------------------------------------------------------------------------- /assets/js/blicki.js: -------------------------------------------------------------------------------- 1 | jQuery(function( $ ) { 2 | 3 | $('.blicki__actions-history').on( 'click', function( e ) { 4 | e.preventDefault(); 5 | $( '.blicky-edit' ).hide(); 6 | $( '.blicky-history' ).slideToggle( 150 ); 7 | } ); 8 | $('.blicki__actions-edit, .blicki__edit-cancel').on( 'click', function( e ) { 9 | e.preventDefault(); 10 | $( '.blicky-history' ).hide(); 11 | $( '.blicky-edit' ).slideToggle( 150 ); 12 | } ); 13 | $('.blicki__edit-cancel').on( 'click', function( e ) { 14 | e.preventDefault(); 15 | $( '.blicky-edit' ).slideUp(); 16 | } ); 17 | $( document ).on( 'click', '.blicki__toc-toggle', function( e ) { 18 | e.preventDefault(); 19 | var toggle = $(this), 20 | tocList = toggle.siblings( '.blicki__toc' ); 21 | if ( tocList.is( ':visible' ) ) { 22 | toggle.text( 'Show' ); 23 | tocList.slideUp( 150 ); 24 | } else { 25 | toggle.text( 'Hide' ); 26 | tocList.slideDown( 150 ); 27 | } 28 | } ); 29 | 30 | $( document ).on( 'click', 'ol.blicki__toc a', function( e ) { 31 | e.preventDefault(); 32 | var heading = $( this ).attr( 'href' ); 33 | $( 'body' ).animate({ scrollTop: $( heading ).offset().top }, 500); 34 | } ); 35 | 36 | $( '.blicky-entry-content' ).each( function() { 37 | var post_div = $( this ); 38 | var headings = []; 39 | var prefix = post_div.attr( 'id' ) 40 | $( 'h1, h2, h3, h4, h5, h6', post_div ).each( function( index ) { 41 | switch($(this).prop('nodeName').toLowerCase()) { 42 | case 'h1': 43 | headings.push({ level: 1, text: $(this).text(), target: '#' + prefix + index }); 44 | break; 45 | case 'h2': 46 | headings.push({ level: 2, text: $(this).text(), target: '#' + prefix + index }); 47 | break; 48 | case 'h3': 49 | headings.push({ level: 3, text: $(this).text(), target: '#' + prefix + index }); 50 | break; 51 | case 'h4': 52 | headings.push({ level: 4, text: $(this).text(), target: '#' + prefix + index }); 53 | break; 54 | case 'h5': 55 | headings.push({ level: 5, text: $(this).text(), target: '#' + prefix + index }); 56 | break; 57 | case 'h6': 58 | headings.push({ level: 6, text: $(this).text(), target: '#' + prefix + index }); 59 | break; 60 | } 61 | $(this).attr('id', prefix + index); 62 | }); 63 | 64 | if ( ! headings.length ) { 65 | return; 66 | } 67 | 68 | // use the highest level heading as the 'base' level 1 indent 69 | var min_level = 0; 70 | headings.forEach( function( elem ) { 71 | if ( 0 === min_level || elem.level < min_level ) { 72 | min_level = elem.level; 73 | } 74 | }); 75 | headings.forEach( function( elem, idx ) { 76 | headings[idx].level = elem.level - min_level + 1; 77 | }); 78 | 79 | var toc_div = $( '
    ' + blicki_js_params.toc + '
    ' ); 80 | var tocToggle = $( 'Show' ); 81 | var list = $( '
      ' ); 82 | headings.forEach( function( elem ) { 83 | var listItem = $('
    1. ').addClass('toc-level' + elem.level); 84 | var link = $('').attr('href', elem.target).text(elem.text); 85 | 86 | listItem.append(link); 87 | list.append(listItem); 88 | }); 89 | var contributorsLink = $( '
    2. ' + blicki_js_params.contributors + '
    3. '); 90 | list.append( contributorsLink ); 91 | toc_div.append( tocToggle, list ); 92 | post_div.prepend( toc_div ) 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function( grunt ) { 2 | 'use strict'; 3 | 4 | grunt.initConfig({ 5 | 6 | // Setting folder templates. 7 | dirs: { 8 | css: 'assets/css', 9 | js: 'assets/js' 10 | }, 11 | 12 | // Minify .js files. 13 | uglify: { 14 | options: { 15 | // Preserve comments that start with a bang. 16 | preserveComments: /^!/ 17 | }, 18 | vendor: { 19 | files: [{ 20 | expand: true, 21 | cwd: '<%= dirs.js %>/', 22 | src: [ 23 | '*.js', 24 | '!*.min.js' 25 | ], 26 | dest: '<%= dirs.js %>/', 27 | ext: '.min.js' 28 | }] 29 | } 30 | }, 31 | 32 | // Compile all .scss files. 33 | sass: { 34 | compile: { 35 | options: { 36 | sourcemap: 'none', 37 | loadPath: require( 'node-bourbon' ).includePaths 38 | }, 39 | files: [{ 40 | expand: true, 41 | cwd: '<%= dirs.css %>/', 42 | src: ['*.scss'], 43 | dest: '<%= dirs.css %>/', 44 | ext: '.css' 45 | }] 46 | } 47 | }, 48 | 49 | // Minify all .css files. 50 | cssmin: { 51 | minify: { 52 | expand: true, 53 | cwd: '<%= dirs.css %>/', 54 | src: ['*.css'], 55 | dest: '<%= dirs.css %>/', 56 | ext: '.css' 57 | } 58 | }, 59 | 60 | // Watch changes for assets. 61 | watch: { 62 | css: { 63 | files: ['<%= dirs.css %>/*.scss'], 64 | tasks: ['sass', 'cssmin'] 65 | }, 66 | js: { 67 | files: [ 68 | '<%= dirs.js %>/*js', 69 | '!<%= dirs.js %>/*.min.js' 70 | ], 71 | tasks: ['uglify'] 72 | } 73 | }, 74 | 75 | // Generate POT files. 76 | makepot: { 77 | options: { 78 | type: 'wp-plugin', 79 | domainPath: 'languages', 80 | potHeaders: { 81 | 'report-msgid-bugs-to': 'https://github.com/Automattic/blicki/issues', 82 | 'language-team': 'LANGUAGE ' 83 | } 84 | }, 85 | dist: { 86 | options: { 87 | potFilename: 'blicki.pot', 88 | exclude: [ 89 | 'tmp/.*' 90 | ] 91 | } 92 | } 93 | }, 94 | 95 | // Check textdomain errors. 96 | checktextdomain: { 97 | options:{ 98 | text_domain: 'blicki', 99 | keywords: [ 100 | '__:1,2d', 101 | '_e:1,2d', 102 | '_x:1,2c,3d', 103 | 'esc_html__:1,2d', 104 | 'esc_html_e:1,2d', 105 | 'esc_html_x:1,2c,3d', 106 | 'esc_attr__:1,2d', 107 | 'esc_attr_e:1,2d', 108 | 'esc_attr_x:1,2c,3d', 109 | '_ex:1,2c,3d', 110 | '_n:1,2,4d', 111 | '_nx:1,2,4c,5d', 112 | '_n_noop:1,2,3d', 113 | '_nx_noop:1,2,3c,4d' 114 | ] 115 | }, 116 | files: { 117 | src: [ 118 | '**/*.php', // Include all files 119 | '!node_modules/**', // Exclude node_modules/ 120 | '!tests/**', // Exclude tests/ 121 | '!vendor/**', // Exclude vendor/ 122 | '!tmp/**' // Exclude tmp/ 123 | ], 124 | expand: true 125 | } 126 | } 127 | }); 128 | 129 | // Load NPM tasks to be used here 130 | grunt.loadNpmTasks( 'grunt-wp-i18n' ); 131 | grunt.loadNpmTasks( 'grunt-checktextdomain' ); 132 | grunt.loadNpmTasks( 'grunt-contrib-uglify' ); 133 | grunt.loadNpmTasks( 'grunt-contrib-sass' ); 134 | grunt.loadNpmTasks( 'grunt-contrib-cssmin' ); 135 | grunt.loadNpmTasks( 'grunt-contrib-watch' ); 136 | 137 | // Register tasks 138 | grunt.registerTask( 'default', [ 139 | 'uglify', 140 | 'css' 141 | ]); 142 | 143 | grunt.registerTask( 'css', [ 144 | 'sass', 145 | 'cssmin' 146 | ]); 147 | 148 | grunt.registerTask( 'dev', [ 149 | 'default', 150 | 'makepot' 151 | ]); 152 | }; 153 | -------------------------------------------------------------------------------- /blicki.php: -------------------------------------------------------------------------------- 1 | includes(); 44 | $this->create_tables(); 45 | Blicki_CPT::register_post_types(); 46 | flush_rewrite_rules(); 47 | } 48 | 49 | /** 50 | * Textdomain. 51 | */ 52 | public function load_plugin_textdomain() { 53 | load_plugin_textdomain( 'blicki', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' ); 54 | } 55 | 56 | /** 57 | * Includes. 58 | */ 59 | public function includes() { 60 | include_once( BLICKI_DIR . 'includes/class-blicki-cpt.php' ); 61 | include_once( BLICKI_DIR . 'includes/class-blicki-suggestion.php' ); 62 | include_once( BLICKI_DIR . 'includes/class-blicki-diff-viewer.php' ); 63 | include_once( BLICKI_DIR . 'includes/class-blicki-edit-form.php' ); 64 | include_once( BLICKI_DIR . 'includes/class-blicki-submit-form.php' ); 65 | include_once( BLICKI_DIR . 'includes/class-blicki-notices.php' ); 66 | include_once( BLICKI_DIR . 'includes/class-blicki-content.php' ); 67 | include_once( BLICKI_DIR . 'includes/class-blicki-shortcodes.php' ); 68 | include_once( BLICKI_DIR . 'includes/class-blicki-history.php' ); 69 | include_once( BLICKI_DIR . 'includes/blicki-core-functions.php' ); 70 | } 71 | 72 | /** 73 | * Create tables. 74 | */ 75 | public function create_tables() { 76 | global $wpdb; 77 | $wpdb->hide_errors(); 78 | require_once( ABSPATH . 'wp-admin/includes/upgrade.php' ); 79 | dbDelta( $this->get_schema() ); 80 | } 81 | 82 | /** 83 | * Get table schema. 84 | * @return string 85 | */ 86 | private static function get_schema() { 87 | global $wpdb; 88 | 89 | if ( $wpdb->has_cap( 'collation' ) ) { 90 | $collate = $wpdb->get_charset_collate(); 91 | } else { 92 | $collate = ''; 93 | } 94 | $tables = " 95 | CREATE TABLE {$wpdb->prefix}blicky_history ( 96 | id bigint(20) NOT NULL AUTO_INCREMENT, 97 | entry_id bigint(20) NOT NULL, 98 | user_id bigint(20) NOT NULL default 0, 99 | revision_id bigint(20) NOT NULL default 0, 100 | user_name longtext NOT NULL, 101 | user_email longtext NOT NULL, 102 | event varchar(20) NOT NULL, 103 | event_timestamp datetime NOT NULL, 104 | PRIMARY KEY (id) 105 | ) $collate; 106 | "; 107 | 108 | return $tables; 109 | } 110 | 111 | /** 112 | * Maybe update blicki. 113 | */ 114 | public function maybe_update() { 115 | $version = get_option( 'blicki_version', 0 ); 116 | 117 | if ( version_compare( $version, BLICKI_VERSION, '<' ) ) { 118 | $this->create_tables(); 119 | update_option( 'blicki_version', BLICKI_VERSION ); 120 | } 121 | } 122 | } 123 | new Blicki(); 124 | -------------------------------------------------------------------------------- /includes/class-blicki-submit-form.php: -------------------------------------------------------------------------------- 1 | 25 |
      26 |
      27 |
      28 | 29 |
      30 | 31 |
      32 | 33 | ' id='title' /> 34 |
      35 | 36 | 37 |
      38 | 39 | 40 |
      41 |
      42 | 43 | 44 |
      45 | 46 | 47 |
      48 | 49 | 50 |
      51 |
      52 |
      53 | handle_submission_form(); 62 | } 63 | } 64 | 65 | /** 66 | * Handle the posted edit form. 67 | */ 68 | private function handle_submission_form() { 69 | try { 70 | if ( is_user_logged_in() ) { 71 | $email = ''; 72 | $name = ''; 73 | $post_author = get_current_user_id(); 74 | } else { 75 | $email = sanitize_text_field( $_POST[ 'blicki-email' ] ); 76 | $name = sanitize_text_field( $_POST[ 'blicki-name' ] ); 77 | $post_author = 0; 78 | } 79 | 80 | $title = sanitize_text_field( $_POST['blicki-title'] ); 81 | $content = wp_kses_post( trim( stripslashes( $_POST['blicki-editor'] ) ) ); 82 | 83 | // Check name is valid. 84 | if ( ! $post_author && empty( $name ) ) { 85 | throw new Exception( __( 'Please enter your name.', 'blicki' ) ); 86 | } 87 | 88 | // Check email is valid. 89 | if ( ! $post_author && ! is_email( $email ) ) { 90 | throw new Exception( __( 'Please enter a valid email address.', 'blicki' ) ); 91 | } 92 | 93 | // Check content is not duplicated 94 | if ( empty( $content ) ) { 95 | throw new Exception( __( 'Please enter some content...', 'blicki' ) ); 96 | } 97 | 98 | // Create pending entry 99 | $entry_id = wp_insert_post( array( 100 | 'post_title' => $title, 101 | 'post_content' => $content, 102 | 'post_author' => $post_author, 103 | 'post_status' => 'pending', 104 | 'post_type' => 'blicki', 105 | ) ); 106 | 107 | update_post_meta( $entry_id, '_blicki_author_email', $email ); 108 | update_post_meta( $entry_id, '_blicki_author_name', $name ); 109 | 110 | Blicki_History::log_event( $entry_id, 'submitted', array( 111 | 'user_id' => $post_author, 112 | 'user_name' => $name, 113 | 'user_email' => $email, 114 | ) ); 115 | Blicki_Notices::add( __( 'Thanks for submitting a new entry. A moderator will approve your entry as soon as possible.', 'blicki' ), 'success' ); 116 | } catch ( Exception $e ) { 117 | Blicki_Notices::add( $e->getMessage(), 'error' ); 118 | } 119 | } 120 | } 121 | new Blicki_Submit_Form(); 122 | -------------------------------------------------------------------------------- /includes/class-blicki-edit-form.php: -------------------------------------------------------------------------------- 1 | 34 |
      35 |
      36 |
      37 | 38 |
      39 | 40 | 41 |
      42 | 43 | /> 44 |
      45 |
      46 | 47 | /> 48 |
      49 | 50 | 51 |
      52 | 53 | 54 | 55 |
      56 |
      57 |
      58 | handle_edit_form( $entry_id ); 71 | } 72 | } 73 | } 74 | 75 | /** 76 | * See if the content has not already been submitted.. 77 | * @param int $entry_id 78 | * @param string $content 79 | * @return boolean 80 | */ 81 | private function is_content_unique( $entry_id, $content ) { 82 | global $wpdb; 83 | 84 | return is_null( $wpdb->get_var( 85 | $wpdb->prepare( 86 | "SELECT 1 FROM $wpdb->posts WHERE post_type='blicki-suggestion' AND post_status='pending' AND post_parent=%d AND post_name=%s;", 87 | $entry_id, 88 | md5( $content ) 89 | ) 90 | ) ); 91 | } 92 | 93 | /** 94 | * Handle the posted edit form. 95 | * @param int $entry_id 96 | */ 97 | private function handle_edit_form( $entry_id ) { 98 | try { 99 | $entry = get_post( $entry_id ); 100 | 101 | if ( is_user_logged_in() ) { 102 | $email = ''; 103 | $name = ''; 104 | $post_author = get_current_user_id(); 105 | } else { 106 | $email = sanitize_text_field( $_POST[ 'blicki-email-' . $entry_id ] ); 107 | $name = sanitize_text_field( $_POST[ 'blicki-name-' . $entry_id ] ); 108 | $post_author = 0; 109 | } 110 | 111 | $content = wp_kses_post( trim( stripslashes( $_POST[ 'blicki-editor-' . $entry_id ] ) ) ); 112 | 113 | // Check name is valid. 114 | if ( ! $post_author && empty( $name ) ) { 115 | throw new Exception( __( 'Please enter your name.', 'blicki' ) ); 116 | } 117 | 118 | // Check email is valid. 119 | if ( ! $post_author && ! is_email( $email ) ) { 120 | throw new Exception( __( 'Please enter a valid email address.', 'blicki' ) ); 121 | } 122 | 123 | // Check content is not duplicated 124 | if ( ! $this->is_content_unique( $entry_id, $content ) ) { 125 | throw new Exception( __( 'This change has already been submitted.', 'blicki' ) ); 126 | } 127 | 128 | $suggestion_id = Blicki_Suggestion::create_suggestion( $entry_id, array( 129 | 'post_title' => $entry->post_title, 130 | 'post_content' => $content, 131 | 'post_author' => $post_author, 132 | 'author_email' => $email, 133 | 'author_name' => $name, 134 | ) ); 135 | 136 | Blicki_Notices::add( __( 'Thanks for submitting your suggestion. A moderator will approve your changes as soon as possible.', 'blicki' ), 'success' ); 137 | } catch ( Exception $e ) { 138 | Blicki_Notices::add( $e->getMessage(), 'error' ); 139 | } 140 | } 141 | } 142 | new Blicki_Edit_Form(); 143 | -------------------------------------------------------------------------------- /includes/class-blicki-diff-viewer.php: -------------------------------------------------------------------------------- 1 | $source_id, 61 | 'post_title' => $suggestion->post_title, 62 | 'post_content' => $suggestion->post_content 63 | ), true ); 64 | if ( is_wp_error( $id ) ) { 65 | $errors = $id->get_error_messages(); 66 | foreach ( $errors as $error ) { 67 | echo $error; 68 | } 69 | wp_die( "Failed to update post", "Update Failed" ); 70 | } 71 | 72 | Blicki_History::log_event( $source_id, 'contributed', array( 73 | 'user_id' => $suggestion->post_author, 74 | 'entry_timestamp' => strtotime( $suggestion->post_date ), 75 | 'user_name' => get_post_meta( $suggestion_id, '_blicki_author_name', true ), 76 | 'user_email' => get_post_meta( $suggestion_id, '_blicki_author_email', true ), 77 | ) ); 78 | 79 | wp_update_post( array( 80 | 'ID' => $suggestion_id, 81 | 'post_status' => 'approved' 82 | ) ); 83 | 84 | echo "

      " . __( 'Suggestion Approved', 'blicki' ) . "

      "; 85 | } else if ( 'reject' === $_REQUEST['action'] ) { 86 | wp_delete_post( $suggestion_id ); 87 | echo "

      " . __( 'Suggestion Rejected', 'blicki' ) . "

      "; 88 | } 89 | echo "Back to post"; 90 | } else { 91 | // this does the diff viewer, also used on front end 92 | $this->show_diffs( $source_id, $suggestion_id ); 93 | } 94 | } 95 | 96 | /** 97 | * Show the diff viewer. 98 | */ 99 | public static function show_diffs( $source_id, $suggestion_id ) { 100 | // get posts, call wp_text_diff 101 | $source = get_post( $source_id ); 102 | $suggestion = get_post( $suggestion_id ); 103 | $nonce_name = 'moderate-post-' . $source_id . '-' . $suggestion_id; 104 | $original_date = date_i18n( get_option( 'date_format' ), strtotime( $source->post_date ) ); 105 | $suggestion_date = date_i18n( get_option( 'date_format' ), strtotime( $suggestion->post_date ) ); 106 | $diff_html = wp_text_diff( 107 | $source->post_title . "\n" . $source->post_content, 108 | $suggestion->post_title . "\n" . $suggestion->post_content, 109 | array( 110 | 'title_left' => sprintf( __( 'Original, %s', 'blicki' ), $original_date ), 111 | 'title_right' => sprintf( __( 'Suggested, %s', 'blicki' ), $suggestion_date ) 112 | ) 113 | ); 114 | ?> 115 |
      116 |

      ' . esc_html( $source->post_title ) . '' ); ?>

      117 | 118 |
      119 | 120 |    121 |    $source_id, 'action' => 'edit', 'merge_from' => $suggestion_id ), 'post.php' ) ); ?>'> 122 |
      123 | 124 | 125 |
      126 | post_type 137 | && ! empty( $suggestion ) && 'blicki-suggestion' === $suggestion->post_type ) { 138 | if ( ! class_exists( 'WP_Text_Diff_Renderer_Table', false ) ) { 139 | require( ABSPATH . WPINC . '/wp-diff.php' ); 140 | } 141 | 142 | $text_diff = new Text_Diff( explode( "\n", $post->post_content ), explode( "\n", $suggestion->post_content ) ); 143 | $merged_text = ''; 144 | foreach ( $text_diff->_edits as $operation ) { 145 | if ( $operation instanceof Text_Diff_Op_copy ) { 146 | // copy just means use the final (or original, they're the same by definition ) 147 | foreach ( $operation->final as $line ) { 148 | $merged_text .= $line . "\n"; 149 | } 150 | } else { 151 | // all other operations have some difference between orig and final (might be false ie empty) 152 | $merged_text .= "ORIGINAL:\n\n"; 153 | if ( ! empty( $operation->orig ) ) { 154 | foreach ( $operation->orig as $line ) { 155 | $merged_text .= $line . "\n"; 156 | } 157 | } 158 | $merged_text .= "\nSUGGESTED:\n\n"; 159 | if ( ! empty( $operation->final ) ) { 160 | foreach ( $operation->final as $line ) { 161 | $merged_text .= $line . "\n"; 162 | } 163 | } 164 | $merged_text .= "\nEND\n\n"; 165 | } 166 | } 167 | return $merged_text; 168 | } 169 | } 170 | 171 | return $content; 172 | } 173 | } 174 | new Blicki_Diff_Viewer(); 175 | -------------------------------------------------------------------------------- /languages/blicki.pot: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Automattic 2 | # This file is distributed under the GPL2+. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: Blicki ¯\\_(ツ)_/¯\n" 6 | "Report-Msgid-Bugs-To: https://github.com/Automattic/blicki/issues\n" 7 | "POT-Creation-Date: 2016-09-20 16:55:59+00:00\n" 8 | "MIME-Version: 1.0\n" 9 | "Content-Type: text/plain; charset=utf-8\n" 10 | "Content-Transfer-Encoding: 8bit\n" 11 | "PO-Revision-Date: 2016-MO-DA HO:MI+ZONE\n" 12 | "Last-Translator: FULL NAME \n" 13 | "Language-Team: LANGUAGE \n" 14 | "X-Generator: grunt-wp-i18n 0.5.4\n" 15 | 16 | #: includes/class-blicki-content.php:27 17 | msgid "Table of Contents" 18 | msgstr "" 19 | 20 | #: includes/class-blicki-content.php:28 21 | msgid "Contributors" 22 | msgstr "" 23 | 24 | #: includes/class-blicki-content.php:47 25 | msgid "%d contribution" 26 | msgid_plural "%d contributions" 27 | msgstr[0] "" 28 | msgstr[1] "" 29 | 30 | #: includes/class-blicki-content.php:93 31 | msgid "Revision Changes" 32 | msgstr "" 33 | 34 | #: includes/class-blicki-content.php:94 35 | #: includes/class-blicki-diff-viewer.php:110 36 | msgid "Original, %s" 37 | msgstr "" 38 | 39 | #: includes/class-blicki-content.php:95 40 | msgid "Revised, %s" 41 | msgstr "" 42 | 43 | #: includes/class-blicki-content.php:102 44 | msgid "Return to entry" 45 | msgstr "" 46 | 47 | #: includes/class-blicki-content.php:117 48 | msgid "Last updated by %s" 49 | msgstr "" 50 | 51 | #: includes/class-blicki-content.php:131 52 | msgid "History" 53 | msgstr "" 54 | 55 | #: includes/class-blicki-content.php:138 includes/class-blicki-cpt.php:42 56 | msgid "Edit" 57 | msgstr "" 58 | 59 | #: includes/class-blicki-content.php:200 60 | #: includes/class-blicki-suggestion.php:195 61 | msgid "Show diff" 62 | msgstr "" 63 | 64 | #: includes/class-blicki-cpt.php:31 65 | msgid "Wiki Entry" 66 | msgstr "" 67 | 68 | #: includes/class-blicki-cpt.php:32 69 | msgid "Wiki Entries" 70 | msgstr "" 71 | 72 | #. Plugin Name of the plugin/theme 73 | msgid "Blicki" 74 | msgstr "" 75 | 76 | #: includes/class-blicki-cpt.php:39 includes/class-blicki-cpt.php:100 77 | msgid "All %s" 78 | msgstr "" 79 | 80 | #: includes/class-blicki-cpt.php:40 81 | msgid "Add New" 82 | msgstr "" 83 | 84 | #: includes/class-blicki-cpt.php:41 85 | msgid "Add %s" 86 | msgstr "" 87 | 88 | #: includes/class-blicki-cpt.php:43 includes/class-blicki-cpt.php:103 89 | msgid "Edit %s" 90 | msgstr "" 91 | 92 | #: includes/class-blicki-cpt.php:44 93 | msgid "New %s" 94 | msgstr "" 95 | 96 | #: includes/class-blicki-cpt.php:45 includes/class-blicki-cpt.php:46 97 | msgid "View %s" 98 | msgstr "" 99 | 100 | #: includes/class-blicki-cpt.php:47 includes/class-blicki-cpt.php:99 101 | msgid "Search %s" 102 | msgstr "" 103 | 104 | #: includes/class-blicki-cpt.php:48 105 | msgid "No %s found" 106 | msgstr "" 107 | 108 | #: includes/class-blicki-cpt.php:49 109 | msgid "No %s found in trash" 110 | msgstr "" 111 | 112 | #: includes/class-blicki-cpt.php:50 includes/class-blicki-cpt.php:101 113 | msgid "Parent %s" 114 | msgstr "" 115 | 116 | #: includes/class-blicki-cpt.php:52 117 | msgid "This is where you can create and manage %s." 118 | msgstr "" 119 | 120 | #: includes/class-blicki-cpt.php:79 121 | msgid "Approved" 122 | msgstr "" 123 | 124 | #: includes/class-blicki-cpt.php:86 125 | msgid "Topic" 126 | msgstr "" 127 | 128 | #: includes/class-blicki-cpt.php:87 includes/class-blicki-cpt.php:165 129 | msgid "Topics" 130 | msgstr "" 131 | 132 | #: includes/class-blicki-cpt.php:102 133 | msgid "Parent %s:" 134 | msgstr "" 135 | 136 | #: includes/class-blicki-cpt.php:104 137 | msgid "Update %s" 138 | msgstr "" 139 | 140 | #: includes/class-blicki-cpt.php:105 141 | msgid "Add New %s" 142 | msgstr "" 143 | 144 | #: includes/class-blicki-cpt.php:106 145 | msgid "New %s Name" 146 | msgstr "" 147 | 148 | #: includes/class-blicki-cpt.php:163 149 | msgid "Pending Suggestions" 150 | msgstr "" 151 | 152 | #: includes/class-blicki-cpt.php:164 153 | msgid "Approved Suggestions" 154 | msgstr "" 155 | 156 | #: includes/class-blicki-diff-viewer.php:25 157 | #: includes/class-blicki-diff-viewer.php:26 158 | msgid "Suggestion Review" 159 | msgstr "" 160 | 161 | #: includes/class-blicki-diff-viewer.php:84 162 | msgid "Suggestion Approved" 163 | msgstr "" 164 | 165 | #: includes/class-blicki-diff-viewer.php:87 166 | msgid "Suggestion Rejected" 167 | msgstr "" 168 | 169 | #: includes/class-blicki-diff-viewer.php:111 170 | msgid "Suggested, %s" 171 | msgstr "" 172 | 173 | #: includes/class-blicki-diff-viewer.php:116 174 | msgid "Merging suggested changes into “%s”" 175 | msgstr "" 176 | 177 | #: includes/class-blicki-diff-viewer.php:119 178 | msgid "Approve Suggestion" 179 | msgstr "" 180 | 181 | #: includes/class-blicki-diff-viewer.php:120 182 | msgid "Reject Suggestion" 183 | msgstr "" 184 | 185 | #: includes/class-blicki-diff-viewer.php:121 186 | msgid "Edit Manually" 187 | msgstr "" 188 | 189 | #: includes/class-blicki-edit-form.php:43 190 | #: includes/class-blicki-submit-form.php:39 191 | msgid "Enter your name:" 192 | msgstr "" 193 | 194 | #: includes/class-blicki-edit-form.php:47 195 | #: includes/class-blicki-submit-form.php:43 196 | msgid "Enter your email address:" 197 | msgstr "" 198 | 199 | #: includes/class-blicki-edit-form.php:53 200 | msgid "Suggest Changes" 201 | msgstr "" 202 | 203 | #: includes/class-blicki-edit-form.php:55 204 | msgid "Cancel" 205 | msgstr "" 206 | 207 | #: includes/class-blicki-edit-form.php:116 208 | #: includes/class-blicki-submit-form.php:86 209 | msgid "Please enter your name." 210 | msgstr "" 211 | 212 | #: includes/class-blicki-edit-form.php:121 213 | #: includes/class-blicki-submit-form.php:91 214 | msgid "Please enter a valid email address." 215 | msgstr "" 216 | 217 | #: includes/class-blicki-edit-form.php:126 218 | msgid "This change has already been submitted." 219 | msgstr "" 220 | 221 | #: includes/class-blicki-edit-form.php:137 222 | msgid "" 223 | "Thanks for submitting your suggestion. A moderator will approve your " 224 | "changes as soon as possible." 225 | msgstr "" 226 | 227 | #: includes/class-blicki-history.php:80 228 | msgid "Submitted by" 229 | msgstr "" 230 | 231 | #: includes/class-blicki-history.php:81 232 | msgid "Updated by" 233 | msgstr "" 234 | 235 | #: includes/class-blicki-history.php:82 236 | msgid "Contribution from" 237 | msgstr "" 238 | 239 | #: includes/class-blicki-history.php:86 240 | msgid "Modified by" 241 | msgstr "" 242 | 243 | #: includes/class-blicki-notices.php:66 244 | msgid "Blicki - There is %d pending suggestion to approve" 245 | msgid_plural "Blicki - There are %d pending suggestions to approve" 246 | msgstr[0] "" 247 | msgstr[1] "" 248 | 249 | #: includes/class-blicki-notices.php:72 250 | msgid "Entries" 251 | msgstr "" 252 | 253 | #: includes/class-blicki-submit-form.php:33 254 | msgid "Enter an entry title:" 255 | msgstr "" 256 | 257 | #: includes/class-blicki-submit-form.php:34 258 | msgid "Entry title" 259 | msgstr "" 260 | 261 | #: includes/class-blicki-submit-form.php:49 262 | msgid "Suggest New Entry" 263 | msgstr "" 264 | 265 | #: includes/class-blicki-submit-form.php:96 266 | msgid "Please enter some content..." 267 | msgstr "" 268 | 269 | #: includes/class-blicki-submit-form.php:116 270 | msgid "" 271 | "Thanks for submitting a new entry. A moderator will approve your entry as " 272 | "soon as possible." 273 | msgstr "" 274 | 275 | #: includes/class-blicki-suggestion.php:100 276 | msgid "Suggest changes" 277 | msgstr "" 278 | 279 | #: includes/class-blicki-suggestion.php:168 280 | msgid "Blicki Suggestions" 281 | msgstr "" 282 | 283 | #: includes/class-blicki-suggestion.php:196 284 | msgid "%d change" 285 | msgid_plural "%d changes" 286 | msgstr[0] "" 287 | msgstr[1] "" 288 | 289 | #: includes/class-blicki-suggestion.php:202 290 | msgid "None yet! (•_•) ( •_•)>⌐■-■ (⌐■_■)" 291 | msgstr "" 292 | 293 | #. Plugin URI of the plugin/theme 294 | msgid "http://dev.wp-plugins.org/browser/blicki/" 295 | msgstr "" 296 | 297 | #. Author of the plugin/theme 298 | msgid "Automattic" 299 | msgstr "" 300 | 301 | #. Author URI of the plugin/theme 302 | msgid "https://automattic.com/" 303 | msgstr "" 304 | 305 | #: includes/class-blicki-content.php:198 306 | msgctxt "Revision by user on date" 307 | msgid "%s %s on %s" 308 | msgstr "" 309 | 310 | #: includes/class-blicki-cpt.php:60 311 | msgctxt "Blicki permalink - resave permalinks after changing this" 312 | msgid "wiki" 313 | msgstr "" 314 | 315 | #: includes/class-blicki-cpt.php:67 316 | msgctxt "Blicki post type archive slug - resave permalinks after changing this" 317 | msgid "wiki" 318 | msgstr "" 319 | 320 | #: includes/class-blicki-cpt.php:112 321 | msgctxt "Wiki topic slug - resave permalinks after changing this" 322 | msgid "wiki-topic" 323 | msgstr "" 324 | 325 | #: includes/class-blicki-suggestion.php:194 326 | msgctxt "Suggestion by user on date" 327 | msgid "Suggestion by %s on %s" 328 | msgstr "" -------------------------------------------------------------------------------- /includes/class-blicki-suggestion.php: -------------------------------------------------------------------------------- 1 | post_type ) { 44 | return false; 45 | } 46 | if ( 'publish' !== $post->post_status ) { 47 | return false; 48 | } 49 | if ( empty( $_POST['blicki-merge-from'] ) ) { 50 | return false; 51 | } 52 | global $wpdb; 53 | 54 | $merging_id = absint( $_POST['blicki-merge-from'] ); 55 | $merging = get_post( $merging_id ); 56 | $wpdb->update( $wpdb->posts, array( 'post_status' => 'approved' ), array( 'ID' => $merging_id ) ); 57 | 58 | Blicki_History::log_event( $post_id, 'contributed', array( 59 | 'user_id' => $merging->post_author, 60 | 'entry_timestamp' => strtotime( $merging->post_date ), 61 | 'user_name' => get_post_meta( $merging_id, '_blicki_author_name', true ), 62 | 'user_email' => get_post_meta( $merging_id, '_blicki_author_email', true ), 63 | ) ); 64 | } 65 | 66 | /** 67 | * True if we're submitting changes, not an update. 68 | * @param int $post_id 69 | * @return boolean 70 | */ 71 | public function is_suggesting_changes( $post_id ) { 72 | if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { 73 | return false; 74 | } 75 | if ( ! $post = get_post( $post_id ) ) { 76 | return false; 77 | } 78 | if ( 'blicki' !== $post->post_type ) { 79 | return false; 80 | } 81 | if ( 'publish' !== $post->post_status ) { 82 | return false; 83 | } 84 | if ( empty( $_POST['blicki-suggest-changes'] ) ) { 85 | return false; 86 | } 87 | return true; 88 | } 89 | 90 | /** 91 | * Allow a post to be saved as a suggestion rather than updated right away. 92 | */ 93 | public function pending_suggestion_button() { 94 | global $post; 95 | 96 | if ( 'blicki' !== $post->post_type ) { 97 | return; 98 | } 99 | 100 | echo '
      '; 101 | if ( isset ( $_GET['merge_from'] ) ) { 102 | echo ""; 103 | } 104 | } 105 | 106 | /** 107 | * Create a suggestion for this wiki entry if needed. 108 | * @param array $data 109 | * @param array $postarr 110 | */ 111 | public function maybe_create_suggestion( $data, $postarr ) { 112 | $post_id = ! empty( $postarr['ID'] ) ? $postarr['ID'] : 0; 113 | 114 | if ( $post_id && $this->is_suggesting_changes( $post_id ) ) { 115 | $old_post_data = get_post( $post_id ); 116 | 117 | // If content has changed, create a suggestion and prevent content from updating. 118 | if ( $old_post_data->post_content !== $data['post_content'] || $old_post_data->post_title !== $data['post_title'] ) { 119 | // Create suggestion. 120 | $this->entry_id = $post_id; 121 | $this->suggestion_id = $this->create_suggestion( $post_id, $data ); 122 | 123 | // Remove content update. 124 | $data['post_content'] = $old_post_data->post_content; 125 | $data['post_title'] = $old_post_data->post_title; 126 | 127 | // Make sure we redirect to the diff view after save. 128 | add_filter( 'redirect_post_location', array( $this, 'redirect_to_diff' ) ); 129 | } 130 | } 131 | 132 | return $data; 133 | } 134 | 135 | /** 136 | * Create a suggestion for this wiki entry. 137 | * @param int $post_id 138 | * @param array $data 139 | */ 140 | public static function create_suggestion( $post_id, $data ) { 141 | $post_id = (int) wp_insert_post( array( 142 | 'post_type' => 'blicki-suggestion', 143 | 'post_name' => md5( $data['post_content'] ), 144 | 'post_title' => $data['post_title'], 145 | 'post_content' => $data['post_content'], 146 | 'post_parent' => $post_id, 147 | 'post_status' => 'pending', 148 | 'post_author' => get_current_user_id(), 149 | ) ); 150 | update_post_meta( $post_id, '_blicki_author_email', isset( $data['author_email'] ) ? $data['author_email'] : '' ); 151 | update_post_meta( $post_id, '_blicki_author_name', isset( $data['author_name'] ) ? $data['author_name'] : '' ); 152 | return $post_id; 153 | } 154 | 155 | /** 156 | * Redirect to diff. 157 | * @param string $url 158 | * @return string 159 | */ 160 | public function redirect_to_diff( $url ) { 161 | return $this->get_diff_viewer_url( $this->entry_id, $this->suggestion_id ); 162 | } 163 | 164 | /** 165 | * Add meta boxes. 166 | */ 167 | public function add_meta_boxes() { 168 | add_meta_box( 'blicki-suggestions', __( 'Blicki Suggestions', 'blicki' ), array( $this, 'blicki_suggestions_content' ), 'blicki', 'side', 'high' ); 169 | } 170 | 171 | /** 172 | * Show suggestions when editing a wiki entry. 173 | */ 174 | public function blicki_suggestions_content() { 175 | global $post; 176 | 177 | if ( ! class_exists( 'WP_Text_Diff_Renderer_Table', false ) ) { 178 | require( ABSPATH . WPINC . '/wp-diff.php' ); 179 | } 180 | 181 | $post_id = $post->ID; 182 | $suggestions = $this->get_suggestions_for_entry( $post_id, 'pending' ); 183 | 184 | if ( $suggestions ) { 185 | echo '
        '; 186 | foreach ( $suggestions as $suggestion_id ) { 187 | $suggestion = get_post( $suggestion_id ); 188 | $date = date_i18n( get_option( 'date_format' ), strtotime( $suggestion->post_date ) ); 189 | $contributor = Blicki_Content::get_contributor_for_post( $suggestion_id ); 190 | $text_diff = new Text_Diff( explode( "\n", $post->post_content ), explode( "\n", $suggestion->post_content ) ); 191 | 192 | echo 193 | '
      • ', 194 | sprintf( esc_html_x( 'Suggestion by %s on %s', 'Suggestion by user on date', 'blicki' ), '' . esc_html( $contributor->name ) . '', $date ), 195 | '
        ', 196 | sprintf( _n( '%d change', '%d changes', sizeof( $text_diff->_edits ), 'blicki' ), sizeof( $text_diff->_edits ) ), 197 | '', 198 | '
      • '; 199 | } 200 | echo '
      '; 201 | } else { 202 | echo '

      ' . __( 'None yet! (•_•) ( •_•)>⌐■-■ (⌐■_■)', 'blicki' ) . '

      '; 203 | } 204 | } 205 | 206 | /** 207 | * Get URL to diff view. 208 | * @param int $source_id 209 | * @param int $suggestion_id 210 | * @return string 211 | */ 212 | public static function get_diff_viewer_url( $source_id, $suggestion_id ) { 213 | return add_query_arg( array( 'page' => 'blicki-show-diff', 'suggestion' => absint( $suggestion_id ), 'source' => absint( $source_id ) ), admin_url( 'edit.php?post_type=blicki' ) ); 214 | } 215 | 216 | /** 217 | * Get IDs of suggestions for an entry. 218 | * @param int $id 219 | * @return int[] 220 | */ 221 | public static function get_suggestions_for_entry( $id, $status = 'any' ) { 222 | return get_posts( array( 223 | 'fields' => 'ids', 224 | 'post_type' => 'blicki-suggestion', 225 | 'post_parent' => $id, 226 | 'posts_per_page' => -1, 227 | 'post_status' => $status, 228 | ) ); 229 | } 230 | } 231 | new Blicki_Suggestion(); 232 | -------------------------------------------------------------------------------- /includes/class-blicki-cpt.php: -------------------------------------------------------------------------------- 1 | array( 36 | 'name' => $plural, 37 | 'singular_name' => $singular, 38 | 'menu_name' => __( 'Blicki', 'blicki' ), 39 | 'all_items' => sprintf( __( 'All %s', 'blicki' ), $plural ), 40 | 'add_new' => __( 'Add New', 'blicki' ), 41 | 'add_new_item' => sprintf( __( 'Add %s', 'blicki' ), $singular ), 42 | 'edit' => __( 'Edit', 'blicki' ), 43 | 'edit_item' => sprintf( __( 'Edit %s', 'blicki' ), $singular ), 44 | 'new_item' => sprintf( __( 'New %s', 'blicki' ), $singular ), 45 | 'view' => sprintf( __( 'View %s', 'blicki' ), $singular ), 46 | 'view_item' => sprintf( __( 'View %s', 'blicki' ), $singular ), 47 | 'search_items' => sprintf( __( 'Search %s', 'blicki' ), $plural ), 48 | 'not_found' => sprintf( __( 'No %s found', 'blicki' ), $plural ), 49 | 'not_found_in_trash' => sprintf( __( 'No %s found in trash', 'blicki' ), $plural ), 50 | 'parent' => sprintf( __( 'Parent %s', 'blicki' ), $singular ), 51 | ), 52 | 'description' => sprintf( __( 'This is where you can create and manage %s.', 'blicki' ), $plural ), 53 | 'public' => true, 54 | 'show_ui' => true, 55 | 'capability_type' => 'post', 56 | 'publicly_queryable' => true, 57 | 'exclude_from_search' => false, 58 | 'hierarchical' => false, 59 | 'rewrite' => array( 60 | 'slug' => _x( 'wiki', 'Blicki permalink - resave permalinks after changing this', 'blicki' ), 61 | 'with_front' => false, 62 | 'feeds' => true, 63 | 'pages' => false, 64 | ), 65 | 'query_var' => true, 66 | 'supports' => array( 'title', 'editor', 'revisions' ), 67 | 'has_archive' => _x( 'wiki', 'Blicki post type archive slug - resave permalinks after changing this', 'blicki' ), 68 | 'show_in_nav_menus' => true, 69 | 'menu_icon' => 'dashicons-carrot', 70 | 'show_in_rest' => true, 71 | ) ) 72 | ); 73 | register_post_type( 'blicki-suggestion', array( 74 | 'public' => false, 75 | 'supports' => array(), 76 | 'show_ui' => false, 77 | ) ); 78 | register_post_status( 'approved', array( 79 | 'label' => __( 'Approved', 'blicki' ), 80 | 'public' => false, 81 | 'exclude_from_search' => false, 82 | 'show_in_admin_all_list' => false, 83 | 'show_in_admin_status_list' => false, 84 | ) ); 85 | 86 | $singular = __( 'Topic', 'blicki' ); 87 | $plural = __( 'Topics', 'blicki' ); 88 | register_taxonomy( 89 | 'blicki_topics', 90 | 'blicki', 91 | apply_filters( 'register_taxonomy_blicki_topics_args', array( 92 | 'hierarchical' => true, 93 | 'update_count_callback' => '_update_post_term_count', 94 | 'label' => $plural, 95 | 'labels' => array( 96 | 'name' => $plural, 97 | 'singular_name' => $singular, 98 | 'menu_name' => ucwords( $plural ), 99 | 'search_items' => sprintf( __( 'Search %s', 'blicki' ), $plural ), 100 | 'all_items' => sprintf( __( 'All %s', 'blicki' ), $plural ), 101 | 'parent_item' => sprintf( __( 'Parent %s', 'blicki' ), $singular ), 102 | 'parent_item_colon' => sprintf( __( 'Parent %s:', 'blicki' ), $singular ), 103 | 'edit_item' => sprintf( __( 'Edit %s', 'blicki' ), $singular ), 104 | 'update_item' => sprintf( __( 'Update %s', 'blicki' ), $singular ), 105 | 'add_new_item' => sprintf( __( 'Add New %s', 'blicki' ), $singular ), 106 | 'new_item_name' => sprintf( __( 'New %s Name', 'blicki' ), $singular ), 107 | ), 108 | 'show_ui' => true, 109 | 'show_tagcloud' => false, 110 | 'public' => true, 111 | 'rewrite' => array( 112 | 'slug' => _x( 'wiki-topic', 'Wiki topic slug - resave permalinks after changing this', 'blicki' ), 113 | 'with_front' => false, 114 | 'hierarchical' => false 115 | ), 116 | ) ) 117 | ); 118 | } 119 | 120 | /** 121 | * Make sure we always keep all revisions for our post type 122 | * @param int $num 123 | * @param object $post 124 | * @return int 125 | */ 126 | public function revisions_to_keep ( $num, $post ) { 127 | if ( 'blicki' === $post->post_type ) { 128 | return -1; 129 | } 130 | return $num; 131 | } 132 | 133 | /** 134 | * When a post is saved, and it's a blicki, update our index of titles/post ids. 135 | * The index is used to add links to entries automagically. 136 | */ 137 | public function update_index( $post_id ) { 138 | if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { 139 | return false; 140 | } 141 | if ( ! $post = get_post( $post_id ) ) { 142 | return false; 143 | } 144 | if ( 'blicki' !== $post->post_type ) { 145 | return false; 146 | } 147 | $blicki_index = get_option( '_blicki_index', array() ); 148 | 149 | if ( 'publish' === $post->post_status ) { 150 | $blicki_index[ $post->ID ] = array( 151 | 'post_title' => $post->post_title, 152 | 'post_link' => '' . esc_html( $post->post_title ) . '', 153 | ); 154 | } else { 155 | unset( $blicki_index[ $post->ID ] ); 156 | } 157 | 158 | update_option( '_blicki_index', $blicki_index ); 159 | } 160 | 161 | public function columns_to_show( $columns ) { 162 | return array_merge( $columns, array( 163 | 'pending_suggestions' => __( 'Pending Suggestions', 'blicki' ), 164 | 'approved_suggestions' => __( 'Approved Suggestions', 'blicki' ), 165 | 'topics' => __( 'Topics', 'blicki' ) 166 | ) ); 167 | } 168 | 169 | public function columns_to_sort( $columns ) { 170 | // topics are not sortable yet, and might not need to be 171 | // TODO need to do lots of magic SQL to actually make these sortable 172 | return array_merge( $columns, array( 173 | 'pending_suggestions' => 'pending_suggestions', 174 | 'approved_suggestions' => 'approved_suggestions' 175 | ) ); 176 | } 177 | 178 | public function data_for_column( $column_name, $post_id ) { 179 | switch ( $column_name ) { 180 | case 'pending_suggestions': 181 | $suggestions = Blicki_Suggestion::get_suggestions_for_entry( $post_id, 'pending' ); 182 | echo count( $suggestions ); 183 | break; 184 | case 'approved_suggestions': 185 | $suggestions = Blicki_Suggestion::get_suggestions_for_entry( $post_id, 'approved' ); 186 | echo count( $suggestions ); 187 | break; 188 | case 'topics': 189 | the_terms( $post_id, 'blicki_topics' ); 190 | break; 191 | } 192 | } 193 | 194 | /** 195 | * Log an edit by a user. 196 | */ 197 | public function create_revision_and_log( $post_id ) { 198 | if ( 'blicki' !== get_post_type( $post_id ) || ! get_current_user_id() ) { 199 | return false; 200 | } 201 | if ( ( $revision_id = wp_save_post_revision( $post_id ) ) && ! is_wp_error( $revision_id ) ) { 202 | Blicki_History::log_event( $post_id, 'updated', array( 203 | 'user_id' => get_current_user_id(), 204 | 'revision_id' => $revision_id, 205 | ) ); 206 | } 207 | } 208 | } 209 | new Blicki_CPT(); 210 | -------------------------------------------------------------------------------- /includes/class-blicki-content.php: -------------------------------------------------------------------------------- 1 | __( 'Table of Contents', 'blicki' ), 29 | 'contributors' => __( 'Contributors', 'blicki' ), 30 | ) ); 31 | } 32 | 33 | public function admin_scripts() { 34 | wp_enqueue_style( 'blicki_css', plugins_url( 'assets/css/blicki.css', BLICKI_FILE ), array( 'revisions' ) ); 35 | } 36 | 37 | /** 38 | * Get contributors. 39 | * @param int $entry_id 40 | * @return string 41 | */ 42 | public function get_contributors_html( $entry_id ) { 43 | $html = ''; 44 | $contributors = $this->get_contributors_for_entry( $entry_id ); 45 | 46 | if ( $contributors ) { 47 | $html .= '

      Contributors

        '; 48 | foreach ( $contributors as $contributor ) { 49 | $html .= '
      1. '; 50 | $html .= get_avatar( $contributor->email, '100' ); 51 | $html .= '
        ' . esc_html( $contributor->name ); 52 | $html .= '
        ' . sprintf( _n( '%d contribution', '%d contributions', $contributor->count, 'blicki' ), $contributor->count ) . '
        '; 53 | $html .= '
      2. '; 54 | } 55 | $html .= '
      '; 56 | } 57 | return $html; 58 | } 59 | 60 | /** 61 | * Filter for 'the_content' to wrap a wiki entry in all our custom code. 62 | */ 63 | public function wrap_wiki( $content ) { 64 | if ( is_singular( 'blicki' ) ) { 65 | global $post; 66 | 67 | // Enqueue our script. 68 | wp_enqueue_script( 'blicki_js' ); 69 | 70 | // construct the wrapped output here as normal 71 | ob_start(); 72 | 73 | // Display notices. 74 | Blicki_Notices::display(); 75 | 76 | if ( isset( $_GET['source'] ) && isset( $_GET['revision'] ) ) { 77 | $source_id = absint( $_GET['source'] ); 78 | $revision_id = absint( $_GET['revision'] ); 79 | 80 | // get posts, call wp_text_diff 81 | if ( 0 == $source_id ) { 82 | $source_text = ''; 83 | } else { 84 | $source = get_post( $source_id ); 85 | $source_text = $source->post_title . "\n" . $source->post_content; 86 | } 87 | 88 | $revision = get_post( $revision_id ); 89 | $revision_text = $revision->post_title . "\n" . $revision->post_content; 90 | 91 | $source_date = date_i18n( get_option( 'date_format' ), strtotime( $source->post_date ) ); 92 | $revision_date = date_i18n( get_option( 'date_format' ), strtotime( $revision->post_date ) ); 93 | 94 | $diff_html = wp_text_diff( 95 | $source_text, 96 | $revision_text, 97 | array( 98 | 'title' => __( 'Revision Changes', 'blicki' ), 99 | 'title_left' => sprintf( __( 'Original, %s', 'blicki' ), $source_date ), 100 | 'title_right' => sprintf( __( 'Revised, %s', 'blicki' ), $revision_date ) 101 | ) 102 | ); 103 | 104 | echo $diff_html; 105 | 106 | // give a link back to the post 107 | echo '' . __( 'Return to entry', 'blicki' ) . ''; 108 | 109 | } else { 110 | // add editor 111 | $editor = Blicki_Edit_Form::get_edit_form( $post->post_content, $post->ID ); 112 | 113 | // grab revision history 114 | $revisions = $this->get_revision_history( $post->ID ); 115 | ?> 116 |
      117 |
      118 |
      119 | get_last_contributor_for_entry( $post->ID ); 121 | printf( 122 | __( 'Last updated by %s', 'blicki' ), 123 | date( 'Y-m-d H:i:s', strtotime( $post->post_modified ) ), 124 | date_i18n( get_option( 'date_format'), strtotime( $post->post_modified ) ), 125 | esc_html( $last_contributor->name ) 126 | ); 127 | ?> 128 |
      129 | 146 |
      147 |
      148 |
      149 | 150 |
      151 |
      152 | 153 |
      154 |
      155 | 156 |
      157 | get_contributors_html( $post->ID ); ?> 158 | Revision History'; 177 | echo '
        '; 178 | $prev_revision_id = 0; 179 | foreach ( array_reverse( $events ) as $event ) { 180 | $date = date_i18n( get_option( 'date_format' ), strtotime( $event->event_timestamp ) ); 181 | 182 | if ( ! empty( $event->user_id ) ) { 183 | $user = get_user_by( 'id', $event->user_id ); 184 | $username = $user->display_name; 185 | $avatar = get_avatar_url( $user->user_email, '100' ); 186 | } else { 187 | $username = $event->user_name; 188 | $avatar = get_avatar_url( $event->user_email, '100' ); 189 | } 190 | 191 | $revisions_url = null; 192 | if ( ! empty( $event->revision_id ) ) { 193 | $revision_id = $event->revision_id; 194 | $revision = get_post( $revision_id ); 195 | 196 | if ( 0 !== $prev_revision_id ) { 197 | // don't show the first diff, nothing to diff against 198 | $revisions_url = add_query_arg( array( 'source' => $prev_revision_id, 'revision' => $revision_id ), get_permalink( $id ) ); 199 | } 200 | $prev_revision_id = $revision_id; 201 | } 202 | 203 | $html = '
      • ' . sprintf( esc_html_x( '%s %s on %s', 'Revision by user on date', 'blicki' ), esc_html( Blicki_History::get_event_display_name( $event->event ) ), '' . esc_html( $username ) . '', esc_html( $date ) ) . '
        '; 204 | if ( ! empty( $revisions_url ) ) { 205 | $html .= '' . esc_html__( 'Show diff', 'blicki' ) . ''; 206 | } 207 | $html .= '
      • '; 208 | $history[] = $html; 209 | } 210 | echo implode( '', array_reverse( $history ) ); 211 | echo '
      '; 212 | } 213 | return ob_get_clean(); 214 | } 215 | 216 | /** 217 | * Format each title callback. 218 | * @return string 219 | */ 220 | private function wrap_post_title( $post_title ) { 221 | return '[' . $post_title . ']'; 222 | } 223 | 224 | /** 225 | * Get post titles from our wiki index. 226 | * @param array $indexes 227 | * @return array 228 | */ 229 | private function get_post_titles_from_index( $indexes ) { 230 | return array_map( array( $this, 'wrap_post_title' ), wp_list_pluck( $indexes, 'post_title' ) ); 231 | } 232 | 233 | /** 234 | * Get post titles from our wiki index. 235 | * @param array $indexes 236 | * @return array 237 | */ 238 | private function get_post_links_from_index( $indexes ) { 239 | return wp_list_pluck( $indexes, 'post_link' ); 240 | } 241 | 242 | /** 243 | * Get URLs. 244 | * @param string $string 245 | * @return array 246 | */ 247 | private function get_internal_urls( $string ) { 248 | $regex = '/' . addcslashes( home_url( '/' ), '/' ) . '[^\" ]+/i'; 249 | preg_match_all( $regex, $string, $matches ); 250 | return ( $matches[0] ); 251 | } 252 | 253 | /** 254 | * Add internal links to our content. 255 | * @todo add transient cache here in the future. 256 | */ 257 | public function add_internal_links( $content, $post_id = 0 ) { 258 | global $post; 259 | 260 | if ( ! $post_id ) { 261 | $post_id = $post->ID; 262 | } 263 | 264 | if ( 'blicki' === get_post_type( $post_id ) && ( $indexes = array_diff_key( get_option( '_blicki_index', array() ), array( $post_id => '' ) ) ) ) { 265 | $content = str_replace( $this->get_post_titles_from_index( $indexes ), $this->get_post_links_from_index( $indexes ), $content ); 266 | } 267 | 268 | return $content; 269 | } 270 | 271 | /** 272 | * Get IDs of suggestions for an entry. 273 | * @param int $id 274 | */ 275 | public function get_last_contributor_for_entry( $id ) { 276 | $events = Blicki_History::get_events( $id ); 277 | 278 | if ( $events ) { 279 | $contributor = $this->get_contributor_from_event( $events[0] ); 280 | } else { 281 | $post = get_post( $id ); 282 | $user = get_user_by( 'id', $post->post_author ); 283 | $contributor = (object) array( 284 | 'id' => $user->user_id, 285 | 'email' => $user->user_email, 286 | 'name' => $user->display_name, 287 | 'count' => 1, 288 | ); 289 | } 290 | return $contributor; 291 | } 292 | 293 | /** 294 | * Get contributor data. 295 | * @return array 296 | */ 297 | private function get_contributor_from_event( $event ) { 298 | if ( $event->user_id ) { 299 | $user = get_user_by( 'id', $event->user_id ); 300 | $id = $event->user_id; 301 | $email = $user->user_email; 302 | $name = $user->display_name; 303 | } else { 304 | $id = $event->user_email; 305 | $email = $event->user_email; 306 | $name = $event->user_name; 307 | } 308 | return (object) array( 309 | 'id' => $event->user_id, 310 | 'email' => $email, 311 | 'name' => $name, 312 | 'count' => 1, 313 | ); 314 | } 315 | 316 | /** 317 | * Get a list of contributors to a wiki entry. 318 | * @param int $entry_id 319 | * @return array 320 | */ 321 | public function get_contributors_for_entry( $entry_id ) { 322 | $contributors = array(); 323 | $events = Blicki_History::get_events( $entry_id ); 324 | foreach ( $events as $event ) { 325 | $contributor = $this->get_contributor_from_event( $event ); 326 | if ( isset( $contributors[ $contributor->id ] ) ) { 327 | $contributors[ $contributor->id ]->count ++; 328 | } else { 329 | $contributors[ $contributor->id ] = $contributor; 330 | } 331 | } 332 | uasort( $contributors, array( $this, 'sort_by_count' ) ); 333 | return array_reverse( $contributors ); 334 | } 335 | 336 | /** 337 | * Sort by count. 338 | */ 339 | private function sort_by_count( $a, $b ) { 340 | if ( $a->count === $b->count ) { 341 | return 0; 342 | } 343 | return ( $a->count < $b->count ) ? -1 : 1; 344 | } 345 | 346 | /** 347 | * Get contributor data. 348 | * @param int $id 349 | * @return object 350 | */ 351 | public static function get_contributor_for_post( $id ) { 352 | $post = get_post( $id ); 353 | 354 | if ( $post->post_author > 0 ) { 355 | $user = get_user_by( 'id', $post->post_author ); 356 | $contributor = (object) array( 357 | 'id' => $user->ID, 358 | 'email' => $user->user_email, 359 | 'name' => $user->display_name, 360 | ); 361 | } else { 362 | $email = get_post_meta( $id, '_blicki_author_email', true ); 363 | $name = get_post_meta( $id, '_blicki_author_name', true ); 364 | $contributor = (object) array( 365 | 'id' => $email, 366 | 'email' => $email, 367 | 'name' => $name, 368 | ); 369 | } 370 | return $contributor; 371 | } 372 | } 373 | new Blicki_Content(); 374 | --------------------------------------------------------------------------------